mirror of
				https://github.com/nsnail/FreeSql.git
				synced 2025-11-04 17:20:49 +08:00 
			
		
		
		
	## v0.9.17 (ODBC)
- 增加 FreeSql.Provider.Odbc,实现 Oracle/SqlServer/MySql 的 Odbc 访问提供; - 增加 FreeSqlBuilder.UseConnectionString 参数 providerType,可解决因包版本冲突时,可能无法反射获得 FreeSql.Provider 对应的类型,通常这个参数不需要设置; - 优化 MaxLength 特性,当指定为 -1 时 DbType 会分别映射类型 text/nvarchar(max)/nvarchar2(4000);
This commit is contained in:
		
							
								
								
									
										30
									
								
								Providers/FreeSql.Provider.Odbc/FreeSql.Provider.Odbc.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								Providers/FreeSql.Provider.Odbc/FreeSql.Provider.Odbc.csproj
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
<Project Sdk="Microsoft.NET.Sdk">
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<TargetFrameworks>netstandard2.0;net45</TargetFrameworks>
 | 
			
		||||
		<Version>0.9.17</Version>
 | 
			
		||||
		<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
 | 
			
		||||
		<Authors>YeXiangQin</Authors>
 | 
			
		||||
		<Description>FreeSql 数据库 Odbc 实现,基于 {Oracle}、{SQL Server}、{MySQL ODBC 8.0 Unicode Driver}</Description>
 | 
			
		||||
		<PackageProjectUrl>https://github.com/2881099/FreeSql</PackageProjectUrl>
 | 
			
		||||
		<RepositoryUrl>https://github.com/2881099/FreeSql</RepositoryUrl>
 | 
			
		||||
		<RepositoryType>git</RepositoryType>
 | 
			
		||||
		<PackageLicenseExpression>MIT</PackageLicenseExpression>
 | 
			
		||||
		<PackageTags>FreeSql;ORM</PackageTags>
 | 
			
		||||
		<PackageId>$(AssemblyName)</PackageId>
 | 
			
		||||
        <PackageIconUrl>https://github.com/2881099/FreeSql/blob/master/logo.png?raw=true</PackageIconUrl>
 | 
			
		||||
		<Title>$(AssemblyName)</Title>
 | 
			
		||||
		<IsPackable>true</IsPackable>
 | 
			
		||||
		<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
 | 
			
		||||
	  <PackageReference Include="System.Data.Odbc" Version="4.5.0" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
    
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
	  <ProjectReference Include="..\..\FreeSql\FreeSql.csproj" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
	
 | 
			
		||||
</Project>
 | 
			
		||||
	
 | 
			
		||||
@@ -0,0 +1,30 @@
 | 
			
		||||
public static partial class FreeSqlOdbcGlobalExtensions
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 特殊处理类似 string.Format 的使用方法,防止注入,以及 IS NULL 转换
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="that"></param>
 | 
			
		||||
    /// <param name="args"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public static string FormatOdbcOracle(this string that, params object[] args) => _odbcOracleAdo.Addslashes(that, args);
 | 
			
		||||
    static FreeSql.Odbc.Oracle.OdbcOracleAdo _odbcOracleAdo = new FreeSql.Odbc.Oracle.OdbcOracleAdo();
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 特殊处理类似 string.Format 的使用方法,防止注入,以及 IS NULL 转换
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="that"></param>
 | 
			
		||||
    /// <param name="args"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public static string FormatOdbcSqlServer(this string that, params object[] args) => _sqlserverAdo.Addslashes(that, args);
 | 
			
		||||
    static FreeSql.Odbc.SqlServer.OdbcSqlServerAdo _sqlserverAdo = new FreeSql.Odbc.SqlServer.OdbcSqlServerAdo();
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 特殊处理类似 string.Format 的使用方法,防止注入,以及 IS NULL 转换
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="that"></param>
 | 
			
		||||
    /// <param name="args"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public static string FormatOdbcMySql(this string that, params object[] args) => _mysqlAdo.Addslashes(that, args);
 | 
			
		||||
    static FreeSql.Odbc.MySql.OdbcMySqlAdo _mysqlAdo = new FreeSql.Odbc.MySql.OdbcMySqlAdo();
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,95 @@
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.MySql
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    class OdbcMySqlDelete<T1> : Internal.CommonProvider.DeleteProvider<T1> where T1 : class
 | 
			
		||||
    {
 | 
			
		||||
        public OdbcMySqlDelete(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere)
 | 
			
		||||
            : base(orm, commonUtils, commonExpression, dywhere)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override List<T1> ExecuteDeleted()
 | 
			
		||||
        {
 | 
			
		||||
            var sql = this.ToSql();
 | 
			
		||||
            if (string.IsNullOrEmpty(sql)) return new List<T1>();
 | 
			
		||||
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            sb.Append(sql).Append(" RETURNING ");
 | 
			
		||||
 | 
			
		||||
            var colidx = 0;
 | 
			
		||||
            foreach (var col in _table.Columns.Values)
 | 
			
		||||
            {
 | 
			
		||||
                if (colidx > 0) sb.Append(", ");
 | 
			
		||||
                sb.Append(_commonUtils.QuoteReadColumn(col.Attribute.MapType, _commonUtils.QuoteSqlName(col.Attribute.Name))).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName));
 | 
			
		||||
                ++colidx;
 | 
			
		||||
            }
 | 
			
		||||
            sql = sb.ToString();
 | 
			
		||||
            var dbParms = _params.ToArray();
 | 
			
		||||
            var before = new Aop.CurdBeforeEventArgs(_table.Type, Aop.CurdType.Delete, sql, dbParms);
 | 
			
		||||
            _orm.Aop.CurdBefore?.Invoke(this, before);
 | 
			
		||||
            var ret = new List<T1>();
 | 
			
		||||
            Exception exception = null;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                ret = _orm.Ado.Query<T1>(_connection, _transaction, CommandType.Text, sql, dbParms);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                exception = ex;
 | 
			
		||||
                throw ex;
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                var after = new Aop.CurdAfterEventArgs(before, exception, ret);
 | 
			
		||||
                _orm.Aop.CurdAfter?.Invoke(this, after);
 | 
			
		||||
            }
 | 
			
		||||
            this.ClearData();
 | 
			
		||||
            return ret;
 | 
			
		||||
        }
 | 
			
		||||
        async public override Task<List<T1>> ExecuteDeletedAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var sql = this.ToSql();
 | 
			
		||||
            if (string.IsNullOrEmpty(sql)) return new List<T1>();
 | 
			
		||||
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            sb.Append(sql).Append(" RETURNING ");
 | 
			
		||||
 | 
			
		||||
            var colidx = 0;
 | 
			
		||||
            foreach (var col in _table.Columns.Values)
 | 
			
		||||
            {
 | 
			
		||||
                if (colidx > 0) sb.Append(", ");
 | 
			
		||||
                sb.Append(_commonUtils.QuoteReadColumn(col.Attribute.MapType, _commonUtils.QuoteSqlName(col.Attribute.Name))).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName));
 | 
			
		||||
                ++colidx;
 | 
			
		||||
            }
 | 
			
		||||
            sql = sb.ToString();
 | 
			
		||||
            var dbParms = _params.ToArray();
 | 
			
		||||
            var before = new Aop.CurdBeforeEventArgs(_table.Type, Aop.CurdType.Delete, sql, dbParms);
 | 
			
		||||
            _orm.Aop.CurdBefore?.Invoke(this, before);
 | 
			
		||||
            var ret = new List<T1>();
 | 
			
		||||
            Exception exception = null;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                ret = await _orm.Ado.QueryAsync<T1>(_connection, _transaction, CommandType.Text, sql, dbParms);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                exception = ex;
 | 
			
		||||
                throw ex;
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                var after = new Aop.CurdAfterEventArgs(before, exception, ret);
 | 
			
		||||
                _orm.Aop.CurdAfter?.Invoke(this, after);
 | 
			
		||||
            }
 | 
			
		||||
            this.ClearData();
 | 
			
		||||
            return ret;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										175
									
								
								Providers/FreeSql.Provider.Odbc/MySql/Curd/OdbcMySqlInsert.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								Providers/FreeSql.Provider.Odbc/MySql/Curd/OdbcMySqlInsert.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,175 @@
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using SafeObjectPool;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data;
 | 
			
		||||
using System.Data.Common;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.MySql
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    class OdbcMySqlInsert<T1> : Internal.CommonProvider.InsertProvider<T1> where T1 : class
 | 
			
		||||
    {
 | 
			
		||||
        public OdbcMySqlInsert(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression)
 | 
			
		||||
            : base(orm, commonUtils, commonExpression)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override int ExecuteAffrows() => base.SplitExecuteAffrows(5000, 3000);
 | 
			
		||||
        public override Task<int> ExecuteAffrowsAsync() => base.SplitExecuteAffrowsAsync(5000, 3000);
 | 
			
		||||
        public override long ExecuteIdentity() => base.SplitExecuteIdentity(5000, 3000);
 | 
			
		||||
        public override Task<long> ExecuteIdentityAsync() => base.SplitExecuteIdentityAsync(5000, 3000);
 | 
			
		||||
        public override List<T1> ExecuteInserted() => base.SplitExecuteInserted(5000, 3000);
 | 
			
		||||
        public override Task<List<T1>> ExecuteInsertedAsync() => base.SplitExecuteInsertedAsync(5000, 3000);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        protected override long RawExecuteIdentity()
 | 
			
		||||
        {
 | 
			
		||||
            var sql = this.ToSql();
 | 
			
		||||
            if (string.IsNullOrEmpty(sql)) return 0;
 | 
			
		||||
 | 
			
		||||
            Object<DbConnection> poolConn = null;
 | 
			
		||||
            var before = new Aop.CurdBeforeEventArgs(_table.Type, Aop.CurdType.Insert, string.Concat(sql, "; SELECT LAST_INSERT_ID();"), _params);
 | 
			
		||||
            _orm.Aop.CurdBefore?.Invoke(this, before);
 | 
			
		||||
            long ret = 0;
 | 
			
		||||
            Exception exception = null;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var conn = _connection;
 | 
			
		||||
                if (_transaction != null) conn = _transaction.Connection;
 | 
			
		||||
                if (conn == null)
 | 
			
		||||
                {
 | 
			
		||||
                    poolConn = _orm.Ado.MasterPool.Get();
 | 
			
		||||
                    conn = poolConn.Value;
 | 
			
		||||
                }
 | 
			
		||||
                _orm.Ado.ExecuteNonQuery(conn, _transaction, CommandType.Text, sql, _params);
 | 
			
		||||
                ret = long.TryParse(string.Concat(_orm.Ado.ExecuteScalar(conn, _transaction, CommandType.Text, "SELECT LAST_INSERT_ID()")), out var trylng) ? trylng : 0;
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                exception = ex;
 | 
			
		||||
                throw ex;
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                if (poolConn != null)
 | 
			
		||||
                    _orm.Ado.MasterPool.Return(poolConn);
 | 
			
		||||
 | 
			
		||||
                var after = new Aop.CurdAfterEventArgs(before, exception, ret);
 | 
			
		||||
                _orm.Aop.CurdAfter?.Invoke(this, after);
 | 
			
		||||
            }
 | 
			
		||||
            return ret;
 | 
			
		||||
        }
 | 
			
		||||
        async protected override Task<long> RawExecuteIdentityAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var sql = this.ToSql();
 | 
			
		||||
            if (string.IsNullOrEmpty(sql)) return 0;
 | 
			
		||||
 | 
			
		||||
            Object<DbConnection> poolConn = null;
 | 
			
		||||
            var before = new Aop.CurdBeforeEventArgs(_table.Type, Aop.CurdType.Insert, string.Concat(sql, "; SELECT LAST_INSERT_ID();"), _params);
 | 
			
		||||
            _orm.Aop.CurdBefore?.Invoke(this, before);
 | 
			
		||||
            long ret = 0;
 | 
			
		||||
            Exception exception = null;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var conn = _connection;
 | 
			
		||||
                if (_transaction != null) conn = _transaction.Connection;
 | 
			
		||||
                if (conn == null)
 | 
			
		||||
                {
 | 
			
		||||
                    poolConn = _orm.Ado.MasterPool.Get();
 | 
			
		||||
                    conn = poolConn.Value;
 | 
			
		||||
                }
 | 
			
		||||
                await _orm.Ado.ExecuteNonQueryAsync(conn, _transaction, CommandType.Text, sql, _params);
 | 
			
		||||
                ret = long.TryParse(string.Concat(await _orm.Ado.ExecuteScalarAsync(conn, _transaction, CommandType.Text, "SELECT LAST_INSERT_ID()")), out var trylng) ? trylng : 0;
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                exception = ex;
 | 
			
		||||
                throw ex;
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                if (poolConn != null)
 | 
			
		||||
                    _orm.Ado.MasterPool.Return(poolConn);
 | 
			
		||||
 | 
			
		||||
                var after = new Aop.CurdAfterEventArgs(before, exception, ret);
 | 
			
		||||
                _orm.Aop.CurdAfter?.Invoke(this, after);
 | 
			
		||||
            }
 | 
			
		||||
            return ret;
 | 
			
		||||
        }
 | 
			
		||||
        protected override List<T1> RawExecuteInserted()
 | 
			
		||||
        {
 | 
			
		||||
            var sql = this.ToSql();
 | 
			
		||||
            if (string.IsNullOrEmpty(sql)) return new List<T1>();
 | 
			
		||||
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            sb.Append(sql).Append(" RETURNING ");
 | 
			
		||||
 | 
			
		||||
            var colidx = 0;
 | 
			
		||||
            foreach (var col in _table.Columns.Values)
 | 
			
		||||
            {
 | 
			
		||||
                if (colidx > 0) sb.Append(", ");
 | 
			
		||||
                sb.Append(_commonUtils.QuoteReadColumn(col.Attribute.MapType, _commonUtils.QuoteSqlName(col.Attribute.Name))).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName));
 | 
			
		||||
                ++colidx;
 | 
			
		||||
            }
 | 
			
		||||
            sql = sb.ToString();
 | 
			
		||||
            var before = new Aop.CurdBeforeEventArgs(_table.Type, Aop.CurdType.Insert, sql, _params);
 | 
			
		||||
            _orm.Aop.CurdBefore?.Invoke(this, before);
 | 
			
		||||
            var ret = new List<T1>();
 | 
			
		||||
            Exception exception = null;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                ret = _orm.Ado.Query<T1>(_connection, _transaction, CommandType.Text, sql, _params);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                exception = ex;
 | 
			
		||||
                throw ex;
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                var after = new Aop.CurdAfterEventArgs(before, exception, ret);
 | 
			
		||||
                _orm.Aop.CurdAfter?.Invoke(this, after);
 | 
			
		||||
            }
 | 
			
		||||
            return ret;
 | 
			
		||||
        }
 | 
			
		||||
        async protected override Task<List<T1>> RawExecuteInsertedAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var sql = this.ToSql();
 | 
			
		||||
            if (string.IsNullOrEmpty(sql)) return new List<T1>();
 | 
			
		||||
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            sb.Append(sql).Append(" RETURNING ");
 | 
			
		||||
 | 
			
		||||
            var colidx = 0;
 | 
			
		||||
            foreach (var col in _table.Columns.Values)
 | 
			
		||||
            {
 | 
			
		||||
                if (colidx > 0) sb.Append(", ");
 | 
			
		||||
                sb.Append(_commonUtils.QuoteReadColumn(col.Attribute.MapType, _commonUtils.QuoteSqlName(col.Attribute.Name))).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName));
 | 
			
		||||
                ++colidx;
 | 
			
		||||
            }
 | 
			
		||||
            sql = sb.ToString();
 | 
			
		||||
            var before = new Aop.CurdBeforeEventArgs(_table.Type, Aop.CurdType.Insert, sql, _params);
 | 
			
		||||
            _orm.Aop.CurdBefore?.Invoke(this, before);
 | 
			
		||||
            var ret = new List<T1>();
 | 
			
		||||
            Exception exception = null;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                ret = await _orm.Ado.QueryAsync<T1>(_connection, _transaction, CommandType.Text, sql, _params);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                exception = ex;
 | 
			
		||||
                throw ex;
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                var after = new Aop.CurdAfterEventArgs(before, exception, ret);
 | 
			
		||||
                _orm.Aop.CurdAfter?.Invoke(this, after);
 | 
			
		||||
            }
 | 
			
		||||
            return ret;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										173
									
								
								Providers/FreeSql.Provider.Odbc/MySql/Curd/OdbcMySqlSelect.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								Providers/FreeSql.Provider.Odbc/MySql/Curd/OdbcMySqlSelect.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,173 @@
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using FreeSql.Internal.Model;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Linq.Expressions;
 | 
			
		||||
using System.Text;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.MySql
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    class OdbcMySqlSelect<T1> : FreeSql.Internal.CommonProvider.Select1Provider<T1> where T1 : class
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        internal static string ToSqlStatic(CommonUtils _commonUtils, CommonExpression _commonExpression, string _select, bool _distinct, string field, StringBuilder _join, StringBuilder _where, string _groupby, string _having, string _orderby, int _skip, int _limit, List<SelectTableInfo> _tables, List<Dictionary<Type, string>> tbUnions, List<LambdaExpression> _whereCascadeExpression, IFreeSql _orm)
 | 
			
		||||
        {
 | 
			
		||||
            if (_orm.CodeFirst.IsAutoSyncStructure)
 | 
			
		||||
                _orm.CodeFirst.SyncStructure(_tables.Select(a => a.Table.Type).ToArray());
 | 
			
		||||
 | 
			
		||||
            if (_whereCascadeExpression.Any())
 | 
			
		||||
                foreach (var tb in _tables.Where(a => a.Type != SelectTableInfoType.Parent))
 | 
			
		||||
                    tb.Cascade = _commonExpression.GetWhereCascadeSql(tb, _whereCascadeExpression);
 | 
			
		||||
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            var tbUnionsGt0 = tbUnions.Count > 1;
 | 
			
		||||
            for (var tbUnionsIdx = 0; tbUnionsIdx < tbUnions.Count; tbUnionsIdx++)
 | 
			
		||||
            {
 | 
			
		||||
                if (tbUnionsIdx > 0) sb.Append(" \r\n\r\nUNION ALL\r\n\r\n");
 | 
			
		||||
                if (tbUnionsGt0) sb.Append("select * from (");
 | 
			
		||||
                var tbUnion = tbUnions[tbUnionsIdx];
 | 
			
		||||
 | 
			
		||||
                var sbnav = new StringBuilder();
 | 
			
		||||
                sb.Append(_select);
 | 
			
		||||
                if (_distinct) sb.Append("DISTINCT ");
 | 
			
		||||
                sb.Append(field).Append(" \r\nFROM ");
 | 
			
		||||
                var tbsjoin = _tables.Where(a => a.Type != SelectTableInfoType.From).ToArray();
 | 
			
		||||
                var tbsfrom = _tables.Where(a => a.Type == SelectTableInfoType.From).ToArray();
 | 
			
		||||
                for (var a = 0; a < tbsfrom.Length; a++)
 | 
			
		||||
                {
 | 
			
		||||
                    sb.Append(_commonUtils.QuoteSqlName(tbUnion[tbsfrom[a].Table.Type])).Append(" ").Append(tbsfrom[a].Alias);
 | 
			
		||||
                    if (tbsjoin.Length > 0)
 | 
			
		||||
                    {
 | 
			
		||||
                        //如果存在 join 查询,则处理 from t1, t2 改为 from t1 inner join t2 on 1 = 1
 | 
			
		||||
                        for (var b = 1; b < tbsfrom.Length; b++)
 | 
			
		||||
                        {
 | 
			
		||||
                            sb.Append(" \r\nLEFT JOIN ").Append(_commonUtils.QuoteSqlName(tbUnion[tbsfrom[b].Table.Type])).Append(" ").Append(tbsfrom[b].Alias);
 | 
			
		||||
 | 
			
		||||
                            if (string.IsNullOrEmpty(tbsfrom[b].NavigateCondition) && string.IsNullOrEmpty(tbsfrom[b].On) && string.IsNullOrEmpty(tbsfrom[b].Cascade)) sb.Append(" ON 1 = 1");
 | 
			
		||||
                            else
 | 
			
		||||
                            {
 | 
			
		||||
                                sb.Append(" ON ").Append(tbsfrom[b].NavigateCondition ?? tbsfrom[b].On);
 | 
			
		||||
                                if (string.IsNullOrEmpty(tbsfrom[b].Cascade) == false) sb.Append(" AND (").Append(tbsfrom[b].Cascade).Append(")");
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        if (!string.IsNullOrEmpty(tbsfrom[a].NavigateCondition)) sbnav.Append(" AND (").Append(tbsfrom[a].NavigateCondition).Append(")");
 | 
			
		||||
                        if (!string.IsNullOrEmpty(tbsfrom[a].On)) sbnav.Append(" AND (").Append(tbsfrom[a].On).Append(")");
 | 
			
		||||
                        if (a > 0 && !string.IsNullOrEmpty(tbsfrom[a].Cascade)) sbnav.Append(" AND (").Append(tbsfrom[a].Cascade).Append(")");
 | 
			
		||||
                    }
 | 
			
		||||
                    if (a < tbsfrom.Length - 1) sb.Append(", ");
 | 
			
		||||
                }
 | 
			
		||||
                foreach (var tb in tbsjoin)
 | 
			
		||||
                {
 | 
			
		||||
                    if (tb.Type == SelectTableInfoType.Parent) continue;
 | 
			
		||||
                    switch (tb.Type)
 | 
			
		||||
                    {
 | 
			
		||||
                        case SelectTableInfoType.LeftJoin:
 | 
			
		||||
                            sb.Append(" \r\nLEFT JOIN ");
 | 
			
		||||
                            break;
 | 
			
		||||
                        case SelectTableInfoType.InnerJoin:
 | 
			
		||||
                            sb.Append(" \r\nINNER JOIN ");
 | 
			
		||||
                            break;
 | 
			
		||||
                        case SelectTableInfoType.RightJoin:
 | 
			
		||||
                            sb.Append(" \r\nRIGHT JOIN ");
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                    sb.Append(_commonUtils.QuoteSqlName(tbUnion[tb.Table.Type])).Append(" ").Append(tb.Alias).Append(" ON ").Append(tb.On ?? tb.NavigateCondition);
 | 
			
		||||
                    if (!string.IsNullOrEmpty(tb.Cascade)) sb.Append(" AND (").Append(tb.Cascade).Append(")");
 | 
			
		||||
                    if (!string.IsNullOrEmpty(tb.On) && !string.IsNullOrEmpty(tb.NavigateCondition)) sbnav.Append(" AND (").Append(tb.NavigateCondition).Append(")");
 | 
			
		||||
                }
 | 
			
		||||
                if (_join.Length > 0) sb.Append(_join);
 | 
			
		||||
 | 
			
		||||
                sbnav.Append(_where);
 | 
			
		||||
                if (!string.IsNullOrEmpty(_tables[0].Cascade))
 | 
			
		||||
                    sbnav.Append(" AND (").Append(_tables[0].Cascade).Append(")");
 | 
			
		||||
 | 
			
		||||
                foreach (var tb in _tables)
 | 
			
		||||
                {
 | 
			
		||||
                    if (tb.Type == SelectTableInfoType.Parent) continue;
 | 
			
		||||
                    if (string.IsNullOrEmpty(tb.Table.SelectFilter) == false)
 | 
			
		||||
                        sbnav.Append(" AND (").Append(tb.Table.SelectFilter.Replace("a.", $"{tb.Alias}.")).Append(")");
 | 
			
		||||
                }
 | 
			
		||||
                if (sbnav.Length > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    sb.Append(" \r\nWHERE ").Append(sbnav.Remove(0, 5));
 | 
			
		||||
                }
 | 
			
		||||
                if (string.IsNullOrEmpty(_groupby) == false)
 | 
			
		||||
                {
 | 
			
		||||
                    sb.Append(_groupby);
 | 
			
		||||
                    if (string.IsNullOrEmpty(_having) == false)
 | 
			
		||||
                        sb.Append(" \r\nHAVING ").Append(_having.Substring(5));
 | 
			
		||||
                }
 | 
			
		||||
                sb.Append(_orderby);
 | 
			
		||||
                if (_skip > 0 || _limit > 0)
 | 
			
		||||
                    sb.Append(" \r\nlimit ").Append(Math.Max(0, _skip)).Append(",").Append(_limit > 0 ? _limit : -1);
 | 
			
		||||
 | 
			
		||||
                sbnav.Clear();
 | 
			
		||||
                if (tbUnionsGt0) sb.Append(") ftb");
 | 
			
		||||
            }
 | 
			
		||||
            return sb.ToString();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public OdbcMySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override ISelect<T1, T2> From<T2>(Expression<Func<ISelectFromExpression<T1>, T2, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new MySqlSelect<T1, T2>(_orm, _commonUtils, _commonExpression, null); OdbcMySqlSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override ISelect<T1, T2, T3> From<T2, T3>(Expression<Func<ISelectFromExpression<T1>, T2, T3, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new MySqlSelect<T1, T2, T3>(_orm, _commonUtils, _commonExpression, null); OdbcMySqlSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override ISelect<T1, T2, T3, T4> From<T2, T3, T4>(Expression<Func<ISelectFromExpression<T1>, T2, T3, T4, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new MySqlSelect<T1, T2, T3, T4>(_orm, _commonUtils, _commonExpression, null); OdbcMySqlSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override ISelect<T1, T2, T3, T4, T5> From<T2, T3, T4, T5>(Expression<Func<ISelectFromExpression<T1>, T2, T3, T4, T5, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new MySqlSelect<T1, T2, T3, T4, T5>(_orm, _commonUtils, _commonExpression, null); OdbcMySqlSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override ISelect<T1, T2, T3, T4, T5, T6> From<T2, T3, T4, T5, T6>(Expression<Func<ISelectFromExpression<T1>, T2, T3, T4, T5, T6, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new MySqlSelect<T1, T2, T3, T4, T5, T6>(_orm, _commonUtils, _commonExpression, null); OdbcMySqlSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override ISelect<T1, T2, T3, T4, T5, T6, T7> From<T2, T3, T4, T5, T6, T7>(Expression<Func<ISelectFromExpression<T1>, T2, T3, T4, T5, T6, T7, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new MySqlSelect<T1, T2, T3, T4, T5, T6, T7>(_orm, _commonUtils, _commonExpression, null); OdbcMySqlSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override ISelect<T1, T2, T3, T4, T5, T6, T7, T8> From<T2, T3, T4, T5, T6, T7, T8>(Expression<Func<ISelectFromExpression<T1>, T2, T3, T4, T5, T6, T7, T8, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new MySqlSelect<T1, T2, T3, T4, T5, T6, T7, T8>(_orm, _commonUtils, _commonExpression, null); OdbcMySqlSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override ISelect<T1, T2, T3, T4, T5, T6, T7, T8, T9> From<T2, T3, T4, T5, T6, T7, T8, T9>(Expression<Func<ISelectFromExpression<T1>, T2, T3, T4, T5, T6, T7, T8, T9, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new MySqlSelect<T1, T2, T3, T4, T5, T6, T7, T8, T9>(_orm, _commonUtils, _commonExpression, null); OdbcMySqlSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override ISelect<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> From<T2, T3, T4, T5, T6, T7, T8, T9, T10>(Expression<Func<ISelectFromExpression<T1>, T2, T3, T4, T5, T6, T7, T8, T9, T10, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new MySqlSelect<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(_orm, _commonUtils, _commonExpression, null); OdbcMySqlSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override string ToSql(string field = null) => ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class MySqlSelect<T1, T2> : FreeSql.Internal.CommonProvider.Select2Provider<T1, T2> where T1 : class where T2 : class
 | 
			
		||||
    {
 | 
			
		||||
        public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcMySqlSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class MySqlSelect<T1, T2, T3> : FreeSql.Internal.CommonProvider.Select3Provider<T1, T2, T3> where T1 : class where T2 : class where T3 : class
 | 
			
		||||
    {
 | 
			
		||||
        public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcMySqlSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class MySqlSelect<T1, T2, T3, T4> : FreeSql.Internal.CommonProvider.Select4Provider<T1, T2, T3, T4> where T1 : class where T2 : class where T3 : class where T4 : class
 | 
			
		||||
    {
 | 
			
		||||
        public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcMySqlSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class MySqlSelect<T1, T2, T3, T4, T5> : FreeSql.Internal.CommonProvider.Select5Provider<T1, T2, T3, T4, T5> where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class
 | 
			
		||||
    {
 | 
			
		||||
        public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcMySqlSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class MySqlSelect<T1, T2, T3, T4, T5, T6> : FreeSql.Internal.CommonProvider.Select6Provider<T1, T2, T3, T4, T5, T6> where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class
 | 
			
		||||
    {
 | 
			
		||||
        public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcMySqlSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class MySqlSelect<T1, T2, T3, T4, T5, T6, T7> : FreeSql.Internal.CommonProvider.Select7Provider<T1, T2, T3, T4, T5, T6, T7> where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class
 | 
			
		||||
    {
 | 
			
		||||
        public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcMySqlSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class MySqlSelect<T1, T2, T3, T4, T5, T6, T7, T8> : FreeSql.Internal.CommonProvider.Select8Provider<T1, T2, T3, T4, T5, T6, T7, T8> where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class
 | 
			
		||||
    {
 | 
			
		||||
        public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcMySqlSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class MySqlSelect<T1, T2, T3, T4, T5, T6, T7, T8, T9> : FreeSql.Internal.CommonProvider.Select9Provider<T1, T2, T3, T4, T5, T6, T7, T8, T9> where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class where T9 : class
 | 
			
		||||
    {
 | 
			
		||||
        public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcMySqlSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class MySqlSelect<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> : FreeSql.Internal.CommonProvider.Select10Provider<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class where T9 : class where T10 : class
 | 
			
		||||
    {
 | 
			
		||||
        public MySqlSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcMySqlSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										140
									
								
								Providers/FreeSql.Provider.Odbc/MySql/Curd/OdbcMySqlUpdate.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								Providers/FreeSql.Provider.Odbc/MySql/Curd/OdbcMySqlUpdate.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,140 @@
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using FreeSql.Internal.Model;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.MySql
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    class OdbcMySqlUpdate<T1> : Internal.CommonProvider.UpdateProvider<T1> where T1 : class
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        public OdbcMySqlUpdate(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere)
 | 
			
		||||
            : base(orm, commonUtils, commonExpression, dywhere)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override int ExecuteAffrows() => base.SplitExecuteAffrows(500, 3000);
 | 
			
		||||
        public override Task<int> ExecuteAffrowsAsync() => base.SplitExecuteAffrowsAsync(500, 3000);
 | 
			
		||||
        public override List<T1> ExecuteUpdated() => base.SplitExecuteUpdated(500, 3000);
 | 
			
		||||
        public override Task<List<T1>> ExecuteUpdatedAsync() => base.SplitExecuteUpdatedAsync(500, 3000);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        protected override List<T1> RawExecuteUpdated()
 | 
			
		||||
        {
 | 
			
		||||
            var sql = this.ToSql();
 | 
			
		||||
            if (string.IsNullOrEmpty(sql)) return new List<T1>();
 | 
			
		||||
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            sb.Append(sql).Append(" RETURNING ");
 | 
			
		||||
 | 
			
		||||
            var colidx = 0;
 | 
			
		||||
            foreach (var col in _table.Columns.Values)
 | 
			
		||||
            {
 | 
			
		||||
                if (colidx > 0) sb.Append(", ");
 | 
			
		||||
                sb.Append(_commonUtils.QuoteReadColumn(col.Attribute.MapType, _commonUtils.QuoteSqlName(col.Attribute.Name))).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName));
 | 
			
		||||
                ++colidx;
 | 
			
		||||
            }
 | 
			
		||||
            sql = sb.ToString();
 | 
			
		||||
            var dbParms = _params.Concat(_paramsSource).ToArray();
 | 
			
		||||
            var before = new Aop.CurdBeforeEventArgs(_table.Type, Aop.CurdType.Update, sql, dbParms);
 | 
			
		||||
            _orm.Aop.CurdBefore?.Invoke(this, before);
 | 
			
		||||
            var ret = new List<T1>();
 | 
			
		||||
            Exception exception = null;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                ret = _orm.Ado.Query<T1>(_connection, _transaction, CommandType.Text, sql, dbParms);
 | 
			
		||||
                ValidateVersionAndThrow(ret.Count);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                exception = ex;
 | 
			
		||||
                throw ex;
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                var after = new Aop.CurdAfterEventArgs(before, exception, ret);
 | 
			
		||||
                _orm.Aop.CurdAfter?.Invoke(this, after);
 | 
			
		||||
            }
 | 
			
		||||
            return ret;
 | 
			
		||||
        }
 | 
			
		||||
        async protected override Task<List<T1>> RawExecuteUpdatedAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var sql = this.ToSql();
 | 
			
		||||
            if (string.IsNullOrEmpty(sql)) return new List<T1>();
 | 
			
		||||
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            sb.Append(sql).Append(" RETURNING ");
 | 
			
		||||
 | 
			
		||||
            var colidx = 0;
 | 
			
		||||
            foreach (var col in _table.Columns.Values)
 | 
			
		||||
            {
 | 
			
		||||
                if (colidx > 0) sb.Append(", ");
 | 
			
		||||
                sb.Append(_commonUtils.QuoteReadColumn(col.Attribute.MapType, _commonUtils.QuoteSqlName(col.Attribute.Name))).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName));
 | 
			
		||||
                ++colidx;
 | 
			
		||||
            }
 | 
			
		||||
            sql = sb.ToString();
 | 
			
		||||
            var dbParms = _params.Concat(_paramsSource).ToArray();
 | 
			
		||||
            var before = new Aop.CurdBeforeEventArgs(_table.Type, Aop.CurdType.Update, sql, dbParms);
 | 
			
		||||
            _orm.Aop.CurdBefore?.Invoke(this, before);
 | 
			
		||||
            var ret = new List<T1>();
 | 
			
		||||
            Exception exception = null;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                ret = await _orm.Ado.QueryAsync<T1>(_connection, _transaction, CommandType.Text, sql, dbParms);
 | 
			
		||||
                ValidateVersionAndThrow(ret.Count);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                exception = ex;
 | 
			
		||||
                throw ex;
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                var after = new Aop.CurdAfterEventArgs(before, exception, ret);
 | 
			
		||||
                _orm.Aop.CurdAfter?.Invoke(this, after);
 | 
			
		||||
            }
 | 
			
		||||
            return ret;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override void ToSqlCase(StringBuilder caseWhen, ColumnInfo[] primarys)
 | 
			
		||||
        {
 | 
			
		||||
            if (_table.Primarys.Length == 1)
 | 
			
		||||
            {
 | 
			
		||||
                caseWhen.Append(_commonUtils.QuoteReadColumn(_table.Primarys.First().Attribute.MapType, _commonUtils.QuoteSqlName(_table.Primarys.First().Attribute.Name)));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            caseWhen.Append("CONCAT(");
 | 
			
		||||
            var pkidx = 0;
 | 
			
		||||
            foreach (var pk in _table.Primarys)
 | 
			
		||||
            {
 | 
			
		||||
                if (pkidx > 0) caseWhen.Append(", ");
 | 
			
		||||
                caseWhen.Append(_commonUtils.QuoteReadColumn(pk.Attribute.MapType, _commonUtils.QuoteSqlName(pk.Attribute.Name)));
 | 
			
		||||
                ++pkidx;
 | 
			
		||||
            }
 | 
			
		||||
            caseWhen.Append(")");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override void ToSqlWhen(StringBuilder sb, ColumnInfo[] primarys, object d)
 | 
			
		||||
        {
 | 
			
		||||
            if (_table.Primarys.Length == 1)
 | 
			
		||||
            {
 | 
			
		||||
                sb.Append(_commonUtils.FormatSql("{0}", _table.Primarys.First().GetMapValue(d)));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            sb.Append("CONCAT(");
 | 
			
		||||
            var pkidx = 0;
 | 
			
		||||
            foreach (var pk in _table.Primarys)
 | 
			
		||||
            {
 | 
			
		||||
                if (pkidx > 0) sb.Append(", ");
 | 
			
		||||
                sb.Append(_commonUtils.FormatSql("{0}", pk.GetMapValue(d)));
 | 
			
		||||
                ++pkidx;
 | 
			
		||||
            }
 | 
			
		||||
            sb.Append(")");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,70 @@
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using SafeObjectPool;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections;
 | 
			
		||||
using System.Data.Common;
 | 
			
		||||
using System.Data.Odbc;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.MySql
 | 
			
		||||
{
 | 
			
		||||
    class OdbcMySqlAdo : FreeSql.Internal.CommonProvider.AdoProvider
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        public OdbcMySqlAdo() : base(DataType.MySql) { }
 | 
			
		||||
        public OdbcMySqlAdo(CommonUtils util, string masterConnectionString, string[] slaveConnectionStrings) : base(DataType.MySql)
 | 
			
		||||
        {
 | 
			
		||||
            base._util = util;
 | 
			
		||||
            if (!string.IsNullOrEmpty(masterConnectionString))
 | 
			
		||||
                MasterPool = new OdbcMySqlConnectionPool("主库", masterConnectionString, null, null);
 | 
			
		||||
            if (slaveConnectionStrings != null)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var slaveConnectionString in slaveConnectionStrings)
 | 
			
		||||
                {
 | 
			
		||||
                    var slavePool = new OdbcMySqlConnectionPool($"从库{SlavePools.Count + 1}", slaveConnectionString, () => Interlocked.Decrement(ref slaveUnavailables), () => Interlocked.Increment(ref slaveUnavailables));
 | 
			
		||||
                    SlavePools.Add(slavePool);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        static DateTime dt1970 = new DateTime(1970, 1, 1);
 | 
			
		||||
        public override object AddslashesProcessParam(object param, Type mapType)
 | 
			
		||||
        {
 | 
			
		||||
            if (param == null) return "NULL";
 | 
			
		||||
            if (mapType != null && mapType != param.GetType())
 | 
			
		||||
                param = Utils.GetDataReaderValue(mapType, param);
 | 
			
		||||
            if (param is bool || param is bool?)
 | 
			
		||||
                return (bool)param ? 1 : 0;
 | 
			
		||||
            else if (param is string || param is char)
 | 
			
		||||
                return string.Concat("'", param.ToString().Replace("'", "''"), "'");
 | 
			
		||||
            else if (param is Enum)
 | 
			
		||||
                return string.Concat("'", param.ToString().Replace("'", "''"), "'"); //((Enum)val).ToInt64();
 | 
			
		||||
            else if (decimal.TryParse(string.Concat(param), out var trydec))
 | 
			
		||||
                return param;
 | 
			
		||||
            else if (param is DateTime || param is DateTime?)
 | 
			
		||||
                return string.Concat("'", ((DateTime)param).ToString("yyyy-MM-dd HH:mm:ss.fff"), "'");
 | 
			
		||||
            else if (param is TimeSpan || param is TimeSpan?)
 | 
			
		||||
                return ((TimeSpan)param).Ticks / 10;
 | 
			
		||||
            else if (param is IEnumerable)
 | 
			
		||||
            {
 | 
			
		||||
                var sb = new StringBuilder();
 | 
			
		||||
                var ie = param as IEnumerable;
 | 
			
		||||
                foreach (var z in ie) sb.Append(",").Append(AddslashesProcessParam(z, mapType));
 | 
			
		||||
                return sb.Length == 0 ? "(NULL)" : sb.Remove(0, 1).Insert(0, "(").Append(")").ToString();
 | 
			
		||||
            }
 | 
			
		||||
            return string.Concat("'", param.ToString().Replace("'", "''"), "'");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override DbCommand CreateCommand()
 | 
			
		||||
        {
 | 
			
		||||
            return new OdbcCommand();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override void ReturnConnection(ObjectPool<DbConnection> pool, Object<DbConnection> conn, Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            (pool as OdbcMySqlConnectionPool).Return(conn, ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override DbParameter[] GetDbParamtersByObject(string sql, object obj) => _util.GetDbParamtersByObject(sql, obj);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,229 @@
 | 
			
		||||
using SafeObjectPool;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data;
 | 
			
		||||
using System.Data.Common;
 | 
			
		||||
using System.Data.Odbc;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.MySql
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    class OdbcMySqlConnectionPool : ObjectPool<DbConnection>
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        internal Action availableHandler;
 | 
			
		||||
        internal Action unavailableHandler;
 | 
			
		||||
 | 
			
		||||
        public OdbcMySqlConnectionPool(string name, string connectionString, Action availableHandler, Action unavailableHandler) : base(null)
 | 
			
		||||
        {
 | 
			
		||||
            var policy = new OdbcMySqlConnectionPoolPolicy
 | 
			
		||||
            {
 | 
			
		||||
                _pool = this,
 | 
			
		||||
                Name = name
 | 
			
		||||
            };
 | 
			
		||||
            this.Policy = policy;
 | 
			
		||||
            policy.ConnectionString = connectionString;
 | 
			
		||||
 | 
			
		||||
            this.availableHandler = availableHandler;
 | 
			
		||||
            this.unavailableHandler = unavailableHandler;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Return(Object<DbConnection> obj, Exception exception, bool isRecreate = false)
 | 
			
		||||
        {
 | 
			
		||||
            if (exception != null && exception is OdbcException)
 | 
			
		||||
            {
 | 
			
		||||
                try { if (obj.Value.Ping() == false) obj.Value.Open(); } catch { base.SetUnavailable(exception); }
 | 
			
		||||
            }
 | 
			
		||||
            base.Return(obj, isRecreate);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    class OdbcMySqlConnectionPoolPolicy : IPolicy<DbConnection>
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        internal OdbcMySqlConnectionPool _pool;
 | 
			
		||||
        public string Name { get; set; } = "MySql OdbcConnection 对象池";
 | 
			
		||||
        public int PoolSize { get; set; } = 100;
 | 
			
		||||
        public TimeSpan SyncGetTimeout { get; set; } = TimeSpan.FromSeconds(10);
 | 
			
		||||
        public TimeSpan IdleTimeout { get; set; } = TimeSpan.Zero;
 | 
			
		||||
        public int AsyncGetCapacity { get; set; } = 10000;
 | 
			
		||||
        public bool IsThrowGetTimeoutException { get; set; } = true;
 | 
			
		||||
        public int CheckAvailableInterval { get; set; } = 5;
 | 
			
		||||
 | 
			
		||||
        static ConcurrentDictionary<string, int> dicConnStrIncr = new ConcurrentDictionary<string, int>(StringComparer.CurrentCultureIgnoreCase);
 | 
			
		||||
        private string _connectionString;
 | 
			
		||||
        public string ConnectionString
 | 
			
		||||
        {
 | 
			
		||||
            get => _connectionString;
 | 
			
		||||
            set
 | 
			
		||||
            {
 | 
			
		||||
                _connectionString = value ?? "";
 | 
			
		||||
 | 
			
		||||
                var pattern = @"Max\s*pool\s*size\s*=\s*(\d+)";
 | 
			
		||||
                var m = Regex.Match(_connectionString, pattern, RegexOptions.IgnoreCase);
 | 
			
		||||
                if (m.Success == false || int.TryParse(m.Groups[1].Value, out var poolsize) == false || poolsize <= 0) poolsize = 100;
 | 
			
		||||
                var connStrIncr = dicConnStrIncr.AddOrUpdate(_connectionString, 1, (oldkey, oldval) => oldval + 1);
 | 
			
		||||
                PoolSize = poolsize + connStrIncr;
 | 
			
		||||
                _connectionString = m.Success ?
 | 
			
		||||
                    Regex.Replace(_connectionString, pattern, $"Max pool size={PoolSize}", RegexOptions.IgnoreCase) :
 | 
			
		||||
                    $"{_connectionString};Max pool size={PoolSize}";
 | 
			
		||||
 | 
			
		||||
                pattern = @"Connection\s*LifeTime\s*=\s*(\d+)";
 | 
			
		||||
                m = Regex.Match(_connectionString, pattern, RegexOptions.IgnoreCase);
 | 
			
		||||
                if (m.Success)
 | 
			
		||||
                {
 | 
			
		||||
                    IdleTimeout = TimeSpan.FromSeconds(int.Parse(m.Groups[1].Value));
 | 
			
		||||
                    _connectionString = Regex.Replace(_connectionString, pattern, "", RegexOptions.IgnoreCase);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var minPoolSize = 0;
 | 
			
		||||
                pattern = @"Min\s*pool\s*size\s*=\s*(\d+)";
 | 
			
		||||
                m = Regex.Match(_connectionString, pattern, RegexOptions.IgnoreCase);
 | 
			
		||||
                if (m.Success)
 | 
			
		||||
                {
 | 
			
		||||
                    minPoolSize = int.Parse(m.Groups[1].Value);
 | 
			
		||||
                    _connectionString = Regex.Replace(_connectionString, pattern, "", RegexOptions.IgnoreCase);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                FreeSql.Internal.CommonUtils.PrevReheatConnectionPool(_pool, minPoolSize);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool OnCheckAvailable(Object<DbConnection> obj)
 | 
			
		||||
        {
 | 
			
		||||
            if (obj.Value.State == ConnectionState.Closed) obj.Value.Open();
 | 
			
		||||
            return obj.Value.Ping(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public DbConnection OnCreate()
 | 
			
		||||
        {
 | 
			
		||||
            var conn = new OdbcConnection(_connectionString);
 | 
			
		||||
            return conn;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnDestroy(DbConnection obj)
 | 
			
		||||
        {
 | 
			
		||||
            if (obj.State != ConnectionState.Closed) obj.Close();
 | 
			
		||||
            obj.Dispose();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnGet(Object<DbConnection> obj)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            if (_pool.IsAvailable)
 | 
			
		||||
            {
 | 
			
		||||
                if (obj.Value == null)
 | 
			
		||||
                {
 | 
			
		||||
                    if (_pool.SetUnavailable(new Exception("连接字符串错误")) == true)
 | 
			
		||||
                        throw new Exception($"【{this.Name}】连接字符串错误,请检查。");
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (obj.Value.State != ConnectionState.Open || DateTime.Now.Subtract(obj.LastReturnTime).TotalSeconds > 60 && obj.Value.Ping() == false)
 | 
			
		||||
                {
 | 
			
		||||
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        obj.Value.Open();
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (Exception ex)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (_pool.SetUnavailable(ex) == true)
 | 
			
		||||
                            throw new Exception($"【{this.Name}】状态不可用,等待后台检查程序恢复方可使用。{ex.Message}");
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        async public Task OnGetAsync(Object<DbConnection> obj)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            if (_pool.IsAvailable)
 | 
			
		||||
            {
 | 
			
		||||
                if (obj.Value == null)
 | 
			
		||||
                {
 | 
			
		||||
                    if (_pool.SetUnavailable(new Exception("连接字符串错误")) == true)
 | 
			
		||||
                        throw new Exception($"【{this.Name}】连接字符串错误,请检查。");
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (obj.Value.State != ConnectionState.Open || DateTime.Now.Subtract(obj.LastReturnTime).TotalSeconds > 60 && (await obj.Value.PingAsync()) == false)
 | 
			
		||||
                {
 | 
			
		||||
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        await obj.Value.OpenAsync();
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (Exception ex)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (_pool.SetUnavailable(ex) == true)
 | 
			
		||||
                            throw new Exception($"【{this.Name}】状态不可用,等待后台检查程序恢复方可使用。{ex.Message}");
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnGetTimeout()
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnReturn(Object<DbConnection> obj)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnAvailable()
 | 
			
		||||
        {
 | 
			
		||||
            _pool.availableHandler?.Invoke();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnUnavailable()
 | 
			
		||||
        {
 | 
			
		||||
            _pool.unavailableHandler?.Invoke();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static class DbConnectionExtensions
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        static DbCommand PingCommand(DbConnection conn)
 | 
			
		||||
        {
 | 
			
		||||
            var cmd = conn.CreateCommand();
 | 
			
		||||
            cmd.CommandTimeout = 5;
 | 
			
		||||
            cmd.CommandText = "select 1";
 | 
			
		||||
            return cmd;
 | 
			
		||||
        }
 | 
			
		||||
        public static bool Ping(this DbConnection that, bool isThrow = false)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                PingCommand(that).ExecuteNonQuery();
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                if (that.State != ConnectionState.Closed) try { that.Close(); } catch { }
 | 
			
		||||
                if (isThrow) throw;
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        async public static Task<bool> PingAsync(this DbConnection that, bool isThrow = false)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                await PingCommand(that).ExecuteNonQueryAsync();
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                if (that.State != ConnectionState.Closed) try { that.Close(); } catch { }
 | 
			
		||||
                if (isThrow) throw;
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										337
									
								
								Providers/FreeSql.Provider.Odbc/MySql/OdbcMySqlCodeFirst.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										337
									
								
								Providers/FreeSql.Provider.Odbc/MySql/OdbcMySqlCodeFirst.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,337 @@
 | 
			
		||||
using FreeSql.DataAnnotations;
 | 
			
		||||
using FreeSql.DatabaseModel;
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using FreeSql.Internal.Model;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data;
 | 
			
		||||
using System.Data.Odbc;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.MySql
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    class OdbcMySqlCodeFirst : Internal.CommonProvider.CodeFirstProvider
 | 
			
		||||
    {
 | 
			
		||||
        public override bool IsNoneCommandParameter { get => true; set => base.IsNoneCommandParameter = true; }
 | 
			
		||||
        public OdbcMySqlCodeFirst(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) : base(orm, commonUtils, commonExpression) { }
 | 
			
		||||
 | 
			
		||||
        static object _dicCsToDbLock = new object();
 | 
			
		||||
        static Dictionary<string, (OdbcType type, string dbtype, string dbtypeFull, bool? isUnsigned, bool? isnullable, object defaultValue)> _dicCsToDb = new Dictionary<string, (OdbcType type, string dbtype, string dbtypeFull, bool? isUnsigned, bool? isnullable, object defaultValue)>() {
 | 
			
		||||
                { typeof(bool).FullName,  (OdbcType.Bit, "bit","bit(1) NOT NULL", null, false, false) },{ typeof(bool?).FullName,  (OdbcType.Bit, "bit","bit(1)", null, true, null) },
 | 
			
		||||
 | 
			
		||||
                { typeof(sbyte).FullName,  (OdbcType.SmallInt, "tinyint", "tinyint(3) NOT NULL", false, false, 0) },{ typeof(sbyte?).FullName,  (OdbcType.SmallInt, "tinyint", "tinyint(3)", false, true, null) },
 | 
			
		||||
                { typeof(short).FullName,  (OdbcType.SmallInt, "smallint","smallint(6) NOT NULL", false, false, 0) },{ typeof(short?).FullName,  (OdbcType.SmallInt, "smallint", "smallint(6)", false, true, null) },
 | 
			
		||||
                { typeof(int).FullName,  (OdbcType.Int, "int", "int(11) NOT NULL", false, false, 0) },{ typeof(int?).FullName,  (OdbcType.Int, "int", "int(11)", false, true, null) },
 | 
			
		||||
                { typeof(long).FullName,  (OdbcType.BigInt, "bigint","bigint(20) NOT NULL", false, false, 0) },{ typeof(long?).FullName,  (OdbcType.BigInt, "bigint","bigint(20)", false, true, null) },
 | 
			
		||||
 | 
			
		||||
                { typeof(byte).FullName,  (OdbcType.TinyInt, "tinyint","tinyint(3) unsigned NOT NULL", true, false, 0) },{ typeof(byte?).FullName,  (OdbcType.TinyInt, "tinyint","tinyint(3) unsigned", true, true, null) },
 | 
			
		||||
                { typeof(ushort).FullName,  (OdbcType.Int, "smallint","smallint(5) unsigned NOT NULL", true, false, 0) },{ typeof(ushort?).FullName,  (OdbcType.Int, "smallint", "smallint(5) unsigned", true, true, null) },
 | 
			
		||||
                { typeof(uint).FullName,  (OdbcType.BigInt, "int", "int(10) unsigned NOT NULL", true, false, 0) },{ typeof(uint?).FullName,  (OdbcType.BigInt, "int", "int(10) unsigned", true, true, null) },
 | 
			
		||||
                { typeof(ulong).FullName,  (OdbcType.Decimal, "bigint", "bigint(20) unsigned NOT NULL", true, false, 0) },{ typeof(ulong?).FullName,  (OdbcType.Decimal, "bigint", "bigint(20) unsigned", true, true, null) },
 | 
			
		||||
 | 
			
		||||
                { typeof(double).FullName,  (OdbcType.Double, "double", "double NOT NULL", false, false, 0) },{ typeof(double?).FullName,  (OdbcType.Double, "double", "double", false, true, null) },
 | 
			
		||||
                { typeof(float).FullName,  (OdbcType.Real, "float","float NOT NULL", false, false, 0) },{ typeof(float?).FullName,  (OdbcType.Real, "float","float", false, true, null) },
 | 
			
		||||
                { typeof(decimal).FullName,  (OdbcType.Decimal, "decimal", "decimal(10,2) NOT NULL", false, false, 0) },{ typeof(decimal?).FullName,  (OdbcType.Decimal, "decimal", "decimal(10,2)", false, true, null) },
 | 
			
		||||
 | 
			
		||||
                { typeof(TimeSpan).FullName,  (OdbcType.Time, "time","time NOT NULL", false, false, 0) },{ typeof(TimeSpan?).FullName,  (OdbcType.Time, "time", "time",false, true, null) },
 | 
			
		||||
                { typeof(DateTime).FullName,  (OdbcType.DateTime, "datetime(3)", "datetime(3) NOT NULL", false, false, new DateTime(1970,1,1)) },{ typeof(DateTime?).FullName,  (OdbcType.DateTime, "datetime(3)", "datetime(3)", false, true, null) },
 | 
			
		||||
 | 
			
		||||
                { typeof(byte[]).FullName,  (OdbcType.VarBinary, "varbinary", "varbinary(255)", false, null, new byte[0]) },
 | 
			
		||||
                { typeof(string).FullName,  (OdbcType.VarChar, "varchar", "varchar(255)", false, null, "") },
 | 
			
		||||
 | 
			
		||||
                { typeof(Guid).FullName,  (OdbcType.VarChar, "char", "char(36) NOT NULL", false, false, Guid.Empty) },{ typeof(Guid?).FullName,  (OdbcType.VarChar, "char", "char(36)", false, true, null) },
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
        public override (int type, string dbtype, string dbtypeFull, bool? isnullable, object defaultValue)? GetDbInfo(Type type)
 | 
			
		||||
        {
 | 
			
		||||
            if (_dicCsToDb.TryGetValue(type.FullName, out var trydc)) return new (int, string, string, bool?, object)?(((int)trydc.type, trydc.dbtype, trydc.dbtypeFull, trydc.isnullable, trydc.defaultValue));
 | 
			
		||||
            if (type.IsArray) return null;
 | 
			
		||||
            var enumType = type.IsEnum ? type : null;
 | 
			
		||||
            if (enumType == null && type.IsNullableType() && type.GenericTypeArguments.Length == 1 && type.GenericTypeArguments.First().IsEnum) enumType = type.GenericTypeArguments.First();
 | 
			
		||||
            if (enumType != null)
 | 
			
		||||
            {
 | 
			
		||||
                var names = string.Join(",", Enum.GetNames(enumType).Select(a => _commonUtils.FormatSql("{0}", a)));
 | 
			
		||||
                var newItem = enumType.GetCustomAttributes(typeof(FlagsAttribute), false).Any() ?
 | 
			
		||||
                    (OdbcType.VarChar, "set", $"set({names}){(type.IsEnum ? " NOT NULL" : "")}", false, type.IsEnum ? false : true, Enum.GetValues(enumType).GetValue(0)) :
 | 
			
		||||
                    (OdbcType.VarChar, "enum", $"enum({names}){(type.IsEnum ? " NOT NULL" : "")}", false, type.IsEnum ? false : true, Enum.GetValues(enumType).GetValue(0));
 | 
			
		||||
                if (_dicCsToDb.ContainsKey(type.FullName) == false)
 | 
			
		||||
                {
 | 
			
		||||
                    lock (_dicCsToDbLock)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (_dicCsToDb.ContainsKey(type.FullName) == false)
 | 
			
		||||
                            _dicCsToDb.Add(type.FullName, newItem);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                return ((int)newItem.Item1, newItem.Item2, newItem.Item3, newItem.Item5, newItem.Item6);
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override string GetComparisonDDLStatements(params Type[] entityTypes)
 | 
			
		||||
        {
 | 
			
		||||
            var conn = _orm.Ado.MasterPool.Get(TimeSpan.FromSeconds(5));
 | 
			
		||||
            var database = conn.Value.Database;
 | 
			
		||||
            Func<string, string, object> ExecuteScalar = (db, sql) =>
 | 
			
		||||
            {
 | 
			
		||||
                if (string.Compare(database, db) != 0) conn.Value.ChangeDatabase(db);
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    using (var cmd = conn.Value.CreateCommand())
 | 
			
		||||
                    {
 | 
			
		||||
                        cmd.CommandText = sql;
 | 
			
		||||
                        cmd.CommandType = CommandType.Text;
 | 
			
		||||
                        return cmd.ExecuteScalar();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                finally
 | 
			
		||||
                {
 | 
			
		||||
                    if (string.Compare(database, db) != 0) conn.Value.ChangeDatabase(database);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var entityType in entityTypes)
 | 
			
		||||
                {
 | 
			
		||||
                    if (sb.Length > 0) sb.Append("\r\n");
 | 
			
		||||
                    var tb = _commonUtils.GetTableByEntity(entityType);
 | 
			
		||||
                    if (tb == null) throw new Exception($"类型 {entityType.FullName} 不可迁移");
 | 
			
		||||
                    if (tb.Columns.Any() == false) throw new Exception($"类型 {entityType.FullName} 不可迁移,可迁移属性0个");
 | 
			
		||||
                    var tbname = tb.DbName.Split(new[] { '.' }, 2);
 | 
			
		||||
                    if (tbname?.Length == 1) tbname = new[] { database, tbname[0] };
 | 
			
		||||
 | 
			
		||||
                    var tboldname = tb.DbOldName?.Split(new[] { '.' }, 2); //旧表名
 | 
			
		||||
                    if (tboldname?.Length == 1) tboldname = new[] { database, tboldname[0] };
 | 
			
		||||
 | 
			
		||||
                    if (string.Compare(tbname[0], database, true) != 0 && ExecuteScalar(database, _commonUtils.FormatSql(" select 1 from information_schema.schemata where schema_name={0}", tbname[0])) == null) //创建数据库
 | 
			
		||||
                        sb.Append($"CREATE DATABASE IF NOT EXISTS ").Append(_commonUtils.QuoteSqlName(tbname[0])).Append(" default charset utf8 COLLATE utf8_general_ci;\r\n");
 | 
			
		||||
 | 
			
		||||
                    var sbalter = new StringBuilder();
 | 
			
		||||
                    var istmpatler = false; //创建临时表,导入数据,删除旧表,修改
 | 
			
		||||
                    if (ExecuteScalar(tbname[0], _commonUtils.FormatSql(" SELECT 1 FROM information_schema.TABLES WHERE table_schema={0} and table_name={1}", tbname)) == null)
 | 
			
		||||
                    { //表不存在
 | 
			
		||||
                        if (tboldname != null)
 | 
			
		||||
                        {
 | 
			
		||||
                            if (string.Compare(tboldname[0], tbname[0], true) != 0 && ExecuteScalar(database, _commonUtils.FormatSql(" select 1 from information_schema.schemata where schema_name={0}", tboldname[0])) == null ||
 | 
			
		||||
                                ExecuteScalar(tboldname[0], _commonUtils.FormatSql(" SELECT 1 FROM information_schema.TABLES WHERE table_schema={0} and table_name={1}", tboldname)) == null)
 | 
			
		||||
                                //数据库或表不存在
 | 
			
		||||
                                tboldname = null;
 | 
			
		||||
                        }
 | 
			
		||||
                        if (tboldname == null)
 | 
			
		||||
                        {
 | 
			
		||||
                            //创建表
 | 
			
		||||
                            sb.Append("CREATE TABLE IF NOT EXISTS ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" ( ");
 | 
			
		||||
                            foreach (var tbcol in tb.ColumnsByPosition)
 | 
			
		||||
                            {
 | 
			
		||||
                                sb.Append(" \r\n  ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ");
 | 
			
		||||
                                sb.Append(tbcol.Attribute.DbType);
 | 
			
		||||
                                if (tbcol.Attribute.IsIdentity == true && tbcol.Attribute.DbType.IndexOf("AUTO_INCREMENT", StringComparison.CurrentCultureIgnoreCase) == -1) sb.Append(" AUTO_INCREMENT");
 | 
			
		||||
                                if (string.IsNullOrEmpty(tbcol.Comment) == false) sb.Append(" COMMENT ").Append(_commonUtils.FormatSql("{0}", tbcol.Comment));
 | 
			
		||||
                                sb.Append(",");
 | 
			
		||||
                            }
 | 
			
		||||
                            if (tb.Primarys.Any())
 | 
			
		||||
                            {
 | 
			
		||||
                                sb.Append(" \r\n  PRIMARY KEY (");
 | 
			
		||||
                                foreach (var tbcol in tb.Primarys) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", ");
 | 
			
		||||
                                sb.Remove(sb.Length - 2, 2).Append("),");
 | 
			
		||||
                            }
 | 
			
		||||
                            foreach (var uk in tb.Uniques)
 | 
			
		||||
                            {
 | 
			
		||||
                                sb.Append(" \r\n  UNIQUE KEY ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append("(");
 | 
			
		||||
                                foreach (var tbcol in uk.Value) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", ");
 | 
			
		||||
                                sb.Remove(sb.Length - 2, 2).Append("),");
 | 
			
		||||
                            }
 | 
			
		||||
                            sb.Remove(sb.Length - 1, 1);
 | 
			
		||||
                            sb.Append("\r\n) Engine=InnoDB;\r\n");
 | 
			
		||||
                            continue;
 | 
			
		||||
                        }
 | 
			
		||||
                        //如果新表,旧表在一个数据库下,直接修改表名
 | 
			
		||||
                        if (string.Compare(tbname[0], tboldname[0], true) == 0)
 | 
			
		||||
                            sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tboldname[0]}.{tboldname[1]}")).Append(" RENAME TO ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(";\r\n");
 | 
			
		||||
                        else
 | 
			
		||||
                        {
 | 
			
		||||
                            //如果新表,旧表不在一起,创建新表,导入数据,删除旧表
 | 
			
		||||
                            istmpatler = true;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                        tboldname = null; //如果新表已经存在,不走改表名逻辑
 | 
			
		||||
 | 
			
		||||
                    //对比字段,只可以修改类型、增加字段、有限的修改字段名;保证安全不删除字段
 | 
			
		||||
                    var sql = _commonUtils.FormatSql(@"
 | 
			
		||||
select
 | 
			
		||||
a.column_name,
 | 
			
		||||
a.column_type,
 | 
			
		||||
case when a.is_nullable = 'YES' then 1 else 0 end 'is_nullable',
 | 
			
		||||
case when locate('auto_increment', a.extra) > 0 then 1 else 0 end 'is_identity',
 | 
			
		||||
a.column_comment 'comment'
 | 
			
		||||
from information_schema.columns a
 | 
			
		||||
where a.table_schema in ({0}) and a.table_name in ({1})", tboldname ?? tbname);
 | 
			
		||||
                    var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql);
 | 
			
		||||
                    var tbstruct = ds.ToDictionary(a => string.Concat(a[0]), a =>
 | 
			
		||||
                    {
 | 
			
		||||
                        var a1 = string.Concat(a[1]);
 | 
			
		||||
                        if (a1 == "datetime") a1 = string.Concat(a1, "(0)");
 | 
			
		||||
                        return new
 | 
			
		||||
                        {
 | 
			
		||||
                            column = string.Concat(a[0]),
 | 
			
		||||
                            sqlType = a1,
 | 
			
		||||
                            is_nullable = string.Concat(a[2]) == "1",
 | 
			
		||||
                            is_identity = string.Concat(a[3]) == "1",
 | 
			
		||||
                            is_unsigned = string.Concat(a[1]).EndsWith(" unsigned"),
 | 
			
		||||
                            comment = string.Concat(a[4])
 | 
			
		||||
                        };
 | 
			
		||||
                    }, StringComparer.CurrentCultureIgnoreCase);
 | 
			
		||||
 | 
			
		||||
                    if (istmpatler == false)
 | 
			
		||||
                    {
 | 
			
		||||
                        var existsPrimary = ExecuteScalar(tbname[0], _commonUtils.FormatSql("select 1 from information_schema.key_column_usage where table_schema={0} and table_name={1} and constraint_name = 'PRIMARY' limit 1", tbname));
 | 
			
		||||
                        foreach (var tbcol in tb.ColumnsByPosition)
 | 
			
		||||
                        {
 | 
			
		||||
                            var isIdentityChanged = tbcol.Attribute.IsIdentity == true && tbcol.Attribute.DbType.IndexOf("AUTO_INCREMENT", StringComparison.CurrentCultureIgnoreCase) == -1;
 | 
			
		||||
                            if (tbstruct.TryGetValue(tbcol.Attribute.Name, out var tbstructcol) ||
 | 
			
		||||
                                string.IsNullOrEmpty(tbcol.Attribute.OldName) == false && tbstruct.TryGetValue(tbcol.Attribute.OldName, out tbstructcol))
 | 
			
		||||
                            {
 | 
			
		||||
                                var isCommentChanged = tbstructcol.comment != (tbcol.Comment ?? "");
 | 
			
		||||
                                if ((tbcol.Attribute.DbType.IndexOf(" unsigned", StringComparison.CurrentCultureIgnoreCase) != -1) != tbstructcol.is_unsigned ||
 | 
			
		||||
                                tbcol.Attribute.DbType.StartsWith(tbstructcol.sqlType, StringComparison.CurrentCultureIgnoreCase) == false ||
 | 
			
		||||
                                tbcol.Attribute.IsNullable != tbstructcol.is_nullable ||
 | 
			
		||||
                                tbcol.Attribute.IsIdentity != tbstructcol.is_identity ||
 | 
			
		||||
                                isCommentChanged)
 | 
			
		||||
                                {
 | 
			
		||||
                                    sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" MODIFY ").Append(_commonUtils.QuoteSqlName(tbstructcol.column)).Append(" ").Append(tbcol.Attribute.DbType);
 | 
			
		||||
                                    if (isCommentChanged) sbalter.Append(" COMMENT ").Append(_commonUtils.FormatSql("{0}", tbcol.Comment ?? ""));
 | 
			
		||||
                                    if (isIdentityChanged) sbalter.Append(" AUTO_INCREMENT").Append(existsPrimary == null ? "" : ", DROP PRIMARY KEY").Append(", ADD PRIMARY KEY(").Append(_commonUtils.QuoteSqlName(tbstructcol.column)).Append(")");
 | 
			
		||||
                                    sbalter.Append(";\r\n");
 | 
			
		||||
                                }
 | 
			
		||||
                                if (string.Compare(tbstructcol.column, tbcol.Attribute.OldName, true) == 0)
 | 
			
		||||
                                {
 | 
			
		||||
                                    //修改列名
 | 
			
		||||
                                    sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" CHANGE COLUMN ").Append(_commonUtils.QuoteSqlName(tbstructcol.column)).Append(" ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ").Append(tbcol.Attribute.DbType);
 | 
			
		||||
                                    if (isCommentChanged) sbalter.Append(" COMMENT ").Append(_commonUtils.FormatSql("{0}", tbcol.Comment ?? ""));
 | 
			
		||||
                                    if (isIdentityChanged) sbalter.Append(" AUTO_INCREMENT").Append(existsPrimary == null ? "" : ", DROP PRIMARY KEY").Append(", ADD PRIMARY KEY(").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(")");
 | 
			
		||||
                                    sbalter.Append(";\r\n");
 | 
			
		||||
                                }
 | 
			
		||||
                                continue;
 | 
			
		||||
                            }
 | 
			
		||||
                            //添加列
 | 
			
		||||
                            sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" ADD ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ").Append(tbcol.Attribute.DbType);
 | 
			
		||||
                            if (tbcol.Attribute.IsNullable == false) sbalter.Append(" DEFAULT ").Append(_commonUtils.FormatSql("{0}", tbcol.Attribute.DbDefautValue));
 | 
			
		||||
                            if (string.IsNullOrEmpty(tbcol.Comment) == false) sbalter.Append(" COMMENT ").Append(_commonUtils.FormatSql("{0}", tbcol.Comment ?? ""));
 | 
			
		||||
                            if (isIdentityChanged) sbalter.Append(" AUTO_INCREMENT").Append(existsPrimary == null ? "" : ", DROP PRIMARY KEY").Append(", ADD PRIMARY KEY(").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(")");
 | 
			
		||||
                            sbalter.Append(";\r\n");
 | 
			
		||||
                        }
 | 
			
		||||
                        var dsuksql = _commonUtils.FormatSql(@"
 | 
			
		||||
select 
 | 
			
		||||
a.column_name,
 | 
			
		||||
a.constraint_name 'index_id'
 | 
			
		||||
from information_schema.key_column_usage a
 | 
			
		||||
where a.constraint_schema IN ({0}) and a.table_name IN ({1})", tboldname ?? tbname);
 | 
			
		||||
                        var dsuk = _orm.Ado.ExecuteArray(CommandType.Text, dsuksql).Select(a => new[] { string.Concat(a[0]), string.Concat(a[1]) });
 | 
			
		||||
                        foreach (var uk in tb.Uniques)
 | 
			
		||||
                        {
 | 
			
		||||
                            if (uk.Key == "PRIMARY" || string.IsNullOrEmpty(uk.Key) || uk.Value.Any() == false) continue;
 | 
			
		||||
                            var dsukfind1 = dsuk.Where(a => string.Compare(a[1], uk.Key, true) == 0).ToArray();
 | 
			
		||||
                            if (dsukfind1.Any() == false || dsukfind1.Length != uk.Value.Count || dsukfind1.Where(a => uk.Value.Where(b => string.Compare(b.Attribute.Name, a[0], true) == 0).Any()).Count() != uk.Value.Count)
 | 
			
		||||
                            {
 | 
			
		||||
                                if (dsukfind1.Any()) sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" DROP INDEX ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append(";\r\n");
 | 
			
		||||
                                sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" ADD CONSTRAINT ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append(" UNIQUE(");
 | 
			
		||||
                                foreach (var tbcol in uk.Value) sbalter.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", ");
 | 
			
		||||
                                sbalter.Remove(sbalter.Length - 2, 2).Append(");\r\n");
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    if (istmpatler == false)
 | 
			
		||||
                    {
 | 
			
		||||
                        sb.Append(sbalter);
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    //创建临时表,数据导进临时表,然后删除原表,将临时表改名为原表名
 | 
			
		||||
                    var tablename = tboldname == null ? _commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}") : _commonUtils.QuoteSqlName($"{tboldname[0]}.{tboldname[1]}");
 | 
			
		||||
                    var tmptablename = _commonUtils.QuoteSqlName($"{tbname[0]}.FreeSqlTmp_{tbname[1]}");
 | 
			
		||||
                    //创建临时表
 | 
			
		||||
                    sb.Append("CREATE TABLE IF NOT EXISTS ").Append(tmptablename).Append(" ( ");
 | 
			
		||||
                    foreach (var tbcol in tb.ColumnsByPosition)
 | 
			
		||||
                    {
 | 
			
		||||
                        sb.Append(" \r\n  ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ");
 | 
			
		||||
                        sb.Append(tbcol.Attribute.DbType);
 | 
			
		||||
                        if (tbcol.Attribute.IsIdentity == true && tbcol.Attribute.DbType.IndexOf("AUTO_INCREMENT", StringComparison.CurrentCultureIgnoreCase) == -1) sb.Append(" AUTO_INCREMENT");
 | 
			
		||||
                        if (string.IsNullOrEmpty(tbcol.Comment) == false) sb.Append(" COMMENT ").Append(_commonUtils.FormatSql("{0}", tbcol.Comment));
 | 
			
		||||
                        sb.Append(",");
 | 
			
		||||
                    }
 | 
			
		||||
                    if (tb.Primarys.Any())
 | 
			
		||||
                    {
 | 
			
		||||
                        sb.Append(" \r\n  PRIMARY KEY (");
 | 
			
		||||
                        foreach (var tbcol in tb.Primarys) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", ");
 | 
			
		||||
                        sb.Remove(sb.Length - 2, 2).Append("),");
 | 
			
		||||
                    }
 | 
			
		||||
                    foreach (var uk in tb.Uniques)
 | 
			
		||||
                    {
 | 
			
		||||
                        sb.Append(" \r\n  UNIQUE KEY ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append("(");
 | 
			
		||||
                        foreach (var tbcol in uk.Value) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", ");
 | 
			
		||||
                        sb.Remove(sb.Length - 2, 2).Append("),");
 | 
			
		||||
                    }
 | 
			
		||||
                    sb.Remove(sb.Length - 1, 1);
 | 
			
		||||
                    sb.Append("\r\n) Engine=InnoDB;\r\n");
 | 
			
		||||
                    sb.Append("INSERT INTO ").Append(tmptablename).Append(" (");
 | 
			
		||||
                    foreach (var tbcol in tb.ColumnsByPosition)
 | 
			
		||||
                        sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", ");
 | 
			
		||||
                    sb.Remove(sb.Length - 2, 2).Append(")\r\nSELECT ");
 | 
			
		||||
                    foreach (var tbcol in tb.ColumnsByPosition)
 | 
			
		||||
                    {
 | 
			
		||||
                        var insertvalue = "NULL";
 | 
			
		||||
                        if (tbstruct.TryGetValue(tbcol.Attribute.Name, out var tbstructcol) ||
 | 
			
		||||
                            string.IsNullOrEmpty(tbcol.Attribute.OldName) == false && tbstruct.TryGetValue(tbcol.Attribute.OldName, out tbstructcol))
 | 
			
		||||
                        {
 | 
			
		||||
                            insertvalue = _commonUtils.QuoteSqlName(tbstructcol.column);
 | 
			
		||||
                            if (tbcol.Attribute.DbType.StartsWith(tbstructcol.sqlType, StringComparison.CurrentCultureIgnoreCase) == false)
 | 
			
		||||
                            {
 | 
			
		||||
                                //insertvalue = $"cast({insertvalue} as {tbcol.Attribute.DbType.Split(' ').First()})";
 | 
			
		||||
                            }
 | 
			
		||||
                            if (tbcol.Attribute.IsNullable != tbstructcol.is_nullable)
 | 
			
		||||
                                insertvalue = $"ifnull({insertvalue},{_commonUtils.FormatSql("{0}", tbcol.Attribute.DbDefautValue)})";
 | 
			
		||||
                        }
 | 
			
		||||
                        else if (tbcol.Attribute.IsNullable == false)
 | 
			
		||||
                            insertvalue = _commonUtils.FormatSql("{0}", tbcol.Attribute.DbDefautValue);
 | 
			
		||||
                        sb.Append(insertvalue).Append(", ");
 | 
			
		||||
                    }
 | 
			
		||||
                    sb.Remove(sb.Length - 2, 2).Append(" FROM ").Append(tablename).Append(";\r\n");
 | 
			
		||||
                    sb.Append("DROP TABLE ").Append(tablename).Append(";\r\n");
 | 
			
		||||
                    sb.Append("ALTER TABLE ").Append(tmptablename).Append(" RENAME TO ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(";\r\n");
 | 
			
		||||
                }
 | 
			
		||||
                return sb.Length == 0 ? null : sb.ToString();
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    conn.Value.ChangeDatabase(database);
 | 
			
		||||
                    _orm.Ado.MasterPool.Return(conn);
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
                {
 | 
			
		||||
                    _orm.Ado.MasterPool.Return(conn, true);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override int ExecuteDDLStatements(string ddl)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrEmpty(ddl)) return 0;
 | 
			
		||||
            var scripts = ddl.Split(new string[] { ";\r\n" }, StringSplitOptions.None).Where(a => string.IsNullOrEmpty(a.Trim()) == false).ToArray();
 | 
			
		||||
 | 
			
		||||
            if (scripts.Any() == false) return 0;
 | 
			
		||||
            if (scripts.Length == 1) return base.ExecuteDDLStatements(ddl);
 | 
			
		||||
 | 
			
		||||
            var affrows = 0;
 | 
			
		||||
            foreach (var script in scripts)
 | 
			
		||||
                affrows += base.ExecuteDDLStatements(script);
 | 
			
		||||
            return affrows;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										385
									
								
								Providers/FreeSql.Provider.Odbc/MySql/OdbcMySqlDbFirst.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										385
									
								
								Providers/FreeSql.Provider.Odbc/MySql/OdbcMySqlDbFirst.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,385 @@
 | 
			
		||||
using FreeSql.DatabaseModel;
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data;
 | 
			
		||||
using System.Data.Odbc;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.MySql
 | 
			
		||||
{
 | 
			
		||||
    class OdbcMySqlDbFirst : IDbFirst
 | 
			
		||||
    {
 | 
			
		||||
        IFreeSql _orm;
 | 
			
		||||
        protected CommonUtils _commonUtils;
 | 
			
		||||
        protected CommonExpression _commonExpression;
 | 
			
		||||
        public OdbcMySqlDbFirst(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression)
 | 
			
		||||
        {
 | 
			
		||||
            _orm = orm;
 | 
			
		||||
            _commonUtils = commonUtils;
 | 
			
		||||
            _commonExpression = commonExpression;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public int GetDbType(DbColumnInfo column) => (int)GetOdbcType(column);
 | 
			
		||||
        OdbcType GetOdbcType(DbColumnInfo column)
 | 
			
		||||
        {
 | 
			
		||||
            var is_unsigned = column.DbTypeTextFull.ToLower().EndsWith(" unsigned");
 | 
			
		||||
            switch (column.DbTypeText.ToLower())
 | 
			
		||||
            {
 | 
			
		||||
                case "bit": return OdbcType.Bit;
 | 
			
		||||
 | 
			
		||||
                case "tinyint": return is_unsigned ? OdbcType.TinyInt : OdbcType.SmallInt;
 | 
			
		||||
                case "smallint": return is_unsigned ? OdbcType.Int : OdbcType.SmallInt;
 | 
			
		||||
                case "mediumint": return is_unsigned ? OdbcType.Int : OdbcType.Int;
 | 
			
		||||
                case "int": return is_unsigned ? OdbcType.BigInt : OdbcType.Int;
 | 
			
		||||
                case "bigint": return is_unsigned ? OdbcType.Decimal : OdbcType.BigInt;
 | 
			
		||||
 | 
			
		||||
                case "real":
 | 
			
		||||
                case "double": return OdbcType.Double;
 | 
			
		||||
                case "float": return OdbcType.Real;
 | 
			
		||||
                case "numeric":
 | 
			
		||||
                case "decimal": return OdbcType.Decimal;
 | 
			
		||||
 | 
			
		||||
                case "year": return OdbcType.Int;
 | 
			
		||||
                case "time": return OdbcType.Time;
 | 
			
		||||
                case "date": return OdbcType.Date;
 | 
			
		||||
                case "timestamp": return OdbcType.Timestamp;
 | 
			
		||||
                case "datetime": return OdbcType.DateTime;
 | 
			
		||||
 | 
			
		||||
                case "tinyblob": return OdbcType.VarBinary;
 | 
			
		||||
                case "blob": return OdbcType.VarBinary;
 | 
			
		||||
                case "mediumblob": return OdbcType.VarBinary;
 | 
			
		||||
                case "longblob": return OdbcType.VarBinary;
 | 
			
		||||
 | 
			
		||||
                case "binary": return OdbcType.Binary;
 | 
			
		||||
                case "varbinary": return OdbcType.VarBinary;
 | 
			
		||||
 | 
			
		||||
                case "tinytext": return OdbcType.Text;
 | 
			
		||||
                case "text": return OdbcType.Text;
 | 
			
		||||
                case "mediumtext": return OdbcType.Text;
 | 
			
		||||
                case "longtext": return OdbcType.Text;
 | 
			
		||||
 | 
			
		||||
                case "char": return column.MaxLength == 36 ? OdbcType.Char : OdbcType.VarChar;
 | 
			
		||||
                case "varchar": return OdbcType.VarChar;
 | 
			
		||||
 | 
			
		||||
                case "set": return OdbcType.VarChar;
 | 
			
		||||
                case "enum": return OdbcType.VarChar;
 | 
			
		||||
 | 
			
		||||
                default: return OdbcType.VarChar;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        static readonly Dictionary<int, (string csConvert, string csParse, string csStringify, string csType, Type csTypeInfo, Type csNullableTypeInfo, string csTypeValue, string dataReaderMethod)> _dicDbToCs = new Dictionary<int, (string csConvert, string csParse, string csStringify, string csType, Type csTypeInfo, Type csNullableTypeInfo, string csTypeValue, string dataReaderMethod)>() {
 | 
			
		||||
                { (int)OdbcType.Bit, ("(bool?)", "{0} == \"1\"", "{0} == true ? \"1\" : \"0\"", "bool?", typeof(bool), typeof(bool?), "{0}.Value", "GetBoolean") },
 | 
			
		||||
 | 
			
		||||
                { (int)OdbcType.TinyInt, ("(sbyte?)", "sbyte.Parse({0})", "{0}.ToString()", "sbyte?", typeof(sbyte), typeof(sbyte?), "{0}.Value", "GetByte") },
 | 
			
		||||
                { (int)OdbcType.SmallInt, ("(short?)", "short.Parse({0})", "{0}.ToString()", "short?", typeof(short), typeof(short?), "{0}.Value", "GetInt16") },
 | 
			
		||||
                { (int)OdbcType.Int, ("(int?)", "int.Parse({0})", "{0}.ToString()", "int?", typeof(int), typeof(int?), "{0}.Value", "GetInt32") },
 | 
			
		||||
                { (int)OdbcType.BigInt, ("(long?)", "long.Parse({0})", "{0}.ToString()", "long?", typeof(long), typeof(long?), "{0}.Value", "GetInt64") },
 | 
			
		||||
 | 
			
		||||
                { (int)OdbcType.Double, ("(double?)", "double.Parse({0})", "{0}.ToString()", "double?", typeof(double), typeof(double?), "{0}.Value", "GetDouble") },
 | 
			
		||||
                { (int)OdbcType.Real, ("(float?)", "float.Parse({0})", "{0}.ToString()", "float?", typeof(float), typeof(float?), "{0}.Value", "GetFloat") },
 | 
			
		||||
                { (int)OdbcType.Decimal, ("(decimal?)", "decimal.Parse({0})", "{0}.ToString()", "decimal?", typeof(decimal), typeof(decimal?), "{0}.Value", "GetDecimal") },
 | 
			
		||||
 | 
			
		||||
                { (int)OdbcType.Time, ("(TimeSpan?)", "TimeSpan.Parse(double.Parse({0}))", "{0}.Ticks.ToString()", "TimeSpan?", typeof(TimeSpan), typeof(TimeSpan?), "{0}.Value", "GetValue") },
 | 
			
		||||
                { (int)OdbcType.Date, ("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") },
 | 
			
		||||
                { (int)OdbcType.Timestamp, ("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") },
 | 
			
		||||
                { (int)OdbcType.DateTime, ("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") },
 | 
			
		||||
 | 
			
		||||
                { (int)OdbcType.Binary, ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", typeof(byte[]), typeof(byte[]), "{0}", "GetValue") },
 | 
			
		||||
                { (int)OdbcType.VarBinary, ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", typeof(byte[]), typeof(byte[]), "{0}", "GetValue") },
 | 
			
		||||
 | 
			
		||||
                { (int)OdbcType.Text, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") },
 | 
			
		||||
 | 
			
		||||
                { (int)OdbcType.Char, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") },
 | 
			
		||||
                { (int)OdbcType.UniqueIdentifier, ("(Guid?)", "Guid.Parse({0})", "{0}.ToString()", "Guid?", typeof(Guid), typeof(Guid?), "{0}", "GetString") },
 | 
			
		||||
                { (int)OdbcType.VarChar, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") },
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
        public string GetCsConvert(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? (column.IsNullable ? trydc.csConvert : trydc.csConvert.Replace("?", "")) : null;
 | 
			
		||||
        public string GetCsParse(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csParse : null;
 | 
			
		||||
        public string GetCsStringify(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csStringify : null;
 | 
			
		||||
        public string GetCsType(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? (column.IsNullable ? trydc.csType : trydc.csType.Replace("?", "")) : null;
 | 
			
		||||
        public Type GetCsTypeInfo(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csTypeInfo : null;
 | 
			
		||||
        public string GetCsTypeValue(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csTypeValue : null;
 | 
			
		||||
        public string GetDataReaderMethod(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.dataReaderMethod : null;
 | 
			
		||||
 | 
			
		||||
        public List<string> GetDatabases()
 | 
			
		||||
        {
 | 
			
		||||
            var sql = @" select schema_name from information_schema.schemata where schema_name not in ('information_schema', 'mysql', 'performance_schema')";
 | 
			
		||||
            var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql);
 | 
			
		||||
            return ds.Select(a => a.FirstOrDefault()?.ToString()).ToList();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public List<DbTableInfo> GetTablesByDatabase(params string[] database2)
 | 
			
		||||
        {
 | 
			
		||||
            var loc1 = new List<DbTableInfo>();
 | 
			
		||||
            var loc2 = new Dictionary<string, DbTableInfo>();
 | 
			
		||||
            var loc3 = new Dictionary<string, Dictionary<string, DbColumnInfo>>();
 | 
			
		||||
            var database = database2?.ToArray();
 | 
			
		||||
 | 
			
		||||
            if (database == null || database.Any() == false)
 | 
			
		||||
            {
 | 
			
		||||
                using (var conn = _orm.Ado.MasterPool.Get())
 | 
			
		||||
                {
 | 
			
		||||
                    if (string.IsNullOrEmpty(conn.Value.Database)) return loc1;
 | 
			
		||||
                    database = new[] { conn.Value.Database };
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            var databaseIn = string.Join(",", database.Select(a => _commonUtils.FormatSql("{0}", a)));
 | 
			
		||||
            var sql = string.Format(@"
 | 
			
		||||
select 
 | 
			
		||||
concat(a.table_schema, '.', a.table_name) 'id',
 | 
			
		||||
a.table_schema 'schema',
 | 
			
		||||
a.table_name 'table',
 | 
			
		||||
a.table_comment,
 | 
			
		||||
a.table_type 'type'
 | 
			
		||||
from information_schema.tables a
 | 
			
		||||
where a.table_schema in ({0})", databaseIn);
 | 
			
		||||
            var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql);
 | 
			
		||||
            if (ds == null) return loc1;
 | 
			
		||||
 | 
			
		||||
            var loc6 = new List<string>();
 | 
			
		||||
            var loc66 = new List<string>();
 | 
			
		||||
            foreach (var row in ds)
 | 
			
		||||
            {
 | 
			
		||||
                var table_id = string.Concat(row[0]);
 | 
			
		||||
                var schema = string.Concat(row[1]);
 | 
			
		||||
                var table = string.Concat(row[2]);
 | 
			
		||||
                var comment = string.Concat(row[3]);
 | 
			
		||||
                var type = string.Concat(row[4]) == "VIEW" ? DbTableType.VIEW : DbTableType.TABLE;
 | 
			
		||||
                if (database.Length == 1)
 | 
			
		||||
                {
 | 
			
		||||
                    table_id = table_id.Substring(table_id.IndexOf('.') + 1);
 | 
			
		||||
                    schema = "";
 | 
			
		||||
                }
 | 
			
		||||
                loc2.Add(table_id, new DbTableInfo { Id = table_id, Schema = schema, Name = table, Comment = comment, Type = type });
 | 
			
		||||
                loc3.Add(table_id, new Dictionary<string, DbColumnInfo>());
 | 
			
		||||
                switch (type)
 | 
			
		||||
                {
 | 
			
		||||
                    case DbTableType.TABLE:
 | 
			
		||||
                    case DbTableType.VIEW:
 | 
			
		||||
                        loc6.Add(table.Replace("'", "''"));
 | 
			
		||||
                        break;
 | 
			
		||||
                    case DbTableType.StoreProcedure:
 | 
			
		||||
                        loc66.Add(table.Replace("'", "''"));
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (loc6.Count == 0) return loc1;
 | 
			
		||||
            var loc8 = "'" + string.Join("','", loc6.ToArray()) + "'";
 | 
			
		||||
            var loc88 = "'" + string.Join("','", loc66.ToArray()) + "'";
 | 
			
		||||
 | 
			
		||||
            sql = string.Format(@"
 | 
			
		||||
select
 | 
			
		||||
concat(a.table_schema, '.', a.table_name),
 | 
			
		||||
a.column_name,
 | 
			
		||||
a.data_type,
 | 
			
		||||
ifnull(a.character_maximum_length, 0) 'len',
 | 
			
		||||
a.column_type,
 | 
			
		||||
case when a.is_nullable = 'YES' then 1 else 0 end 'is_nullable',
 | 
			
		||||
case when locate('auto_increment', a.extra) > 0 then 1 else 0 end 'is_identity',
 | 
			
		||||
a.column_comment 'comment'
 | 
			
		||||
from information_schema.columns a
 | 
			
		||||
where a.table_schema in ({1}) and a.table_name in ({0})
 | 
			
		||||
", loc8, databaseIn);
 | 
			
		||||
            ds = _orm.Ado.ExecuteArray(CommandType.Text, sql);
 | 
			
		||||
            if (ds == null) return loc1;
 | 
			
		||||
 | 
			
		||||
            foreach (var row in ds)
 | 
			
		||||
            {
 | 
			
		||||
                string table_id = string.Concat(row[0]);
 | 
			
		||||
                string column = string.Concat(row[1]);
 | 
			
		||||
                string type = string.Concat(row[2]);
 | 
			
		||||
                //long max_length = long.Parse(string.Concat(row[3]));
 | 
			
		||||
                string sqlType = string.Concat(row[4]);
 | 
			
		||||
                var m_len = Regex.Match(sqlType, @"\w+\((\d+)");
 | 
			
		||||
                int max_length = m_len.Success ? int.Parse(m_len.Groups[1].Value) : -1;
 | 
			
		||||
                bool is_nullable = string.Concat(row[5]) == "1";
 | 
			
		||||
                bool is_identity = string.Concat(row[6]) == "1";
 | 
			
		||||
                string comment = string.Concat(row[7]);
 | 
			
		||||
                if (max_length == 0) max_length = -1;
 | 
			
		||||
                if (database.Length == 1)
 | 
			
		||||
                {
 | 
			
		||||
                    table_id = table_id.Substring(table_id.IndexOf('.') + 1);
 | 
			
		||||
                }
 | 
			
		||||
                loc3[table_id].Add(column, new DbColumnInfo
 | 
			
		||||
                {
 | 
			
		||||
                    Name = column,
 | 
			
		||||
                    MaxLength = max_length,
 | 
			
		||||
                    IsIdentity = is_identity,
 | 
			
		||||
                    IsNullable = is_nullable,
 | 
			
		||||
                    IsPrimary = false,
 | 
			
		||||
                    DbTypeText = type,
 | 
			
		||||
                    DbTypeTextFull = sqlType,
 | 
			
		||||
                    Table = loc2[table_id],
 | 
			
		||||
                    Coment = comment
 | 
			
		||||
                });
 | 
			
		||||
                loc3[table_id][column].DbType = this.GetDbType(loc3[table_id][column]);
 | 
			
		||||
                loc3[table_id][column].CsType = this.GetCsTypeInfo(loc3[table_id][column]);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            sql = string.Format(@"
 | 
			
		||||
select 
 | 
			
		||||
concat(a.constraint_schema, '.', a.table_name) 'table_id',
 | 
			
		||||
a.column_name,
 | 
			
		||||
a.constraint_name 'index_id',
 | 
			
		||||
1 'IsUnique',
 | 
			
		||||
case when a.constraint_name = 'PRIMARY' then 1 else 0 end 'IsPrimaryKey',
 | 
			
		||||
0 'IsClustered',
 | 
			
		||||
0 'IsDesc'
 | 
			
		||||
from information_schema.key_column_usage a
 | 
			
		||||
where a.constraint_schema in ({1}) and a.table_name in ({0}) and isnull(position_in_unique_constraint)
 | 
			
		||||
", loc8, databaseIn);
 | 
			
		||||
            ds = _orm.Ado.ExecuteArray(CommandType.Text, sql);
 | 
			
		||||
            if (ds == null) return loc1;
 | 
			
		||||
 | 
			
		||||
            var indexColumns = new Dictionary<string, Dictionary<string, List<DbColumnInfo>>>();
 | 
			
		||||
            var uniqueColumns = new Dictionary<string, Dictionary<string, List<DbColumnInfo>>>();
 | 
			
		||||
            foreach (var row in ds)
 | 
			
		||||
            {
 | 
			
		||||
                string table_id = string.Concat(row[0]);
 | 
			
		||||
                string column = string.Concat(row[1]);
 | 
			
		||||
                string index_id = string.Concat(row[2]);
 | 
			
		||||
                bool is_unique = string.Concat(row[3]) == "1";
 | 
			
		||||
                bool is_primary_key = string.Concat(row[4]) == "1";
 | 
			
		||||
                bool is_clustered = string.Concat(row[5]) == "1";
 | 
			
		||||
                int is_desc = int.Parse(string.Concat(row[6]));
 | 
			
		||||
                if (database.Length == 1)
 | 
			
		||||
                {
 | 
			
		||||
                    table_id = table_id.Substring(table_id.IndexOf('.') + 1);
 | 
			
		||||
                }
 | 
			
		||||
                if (loc3.ContainsKey(table_id) == false || loc3[table_id].ContainsKey(column) == false) continue;
 | 
			
		||||
                var loc9 = loc3[table_id][column];
 | 
			
		||||
                if (loc9.IsPrimary == false && is_primary_key) loc9.IsPrimary = is_primary_key;
 | 
			
		||||
 | 
			
		||||
                Dictionary<string, List<DbColumnInfo>> loc10 = null;
 | 
			
		||||
                List<DbColumnInfo> loc11 = null;
 | 
			
		||||
                if (!indexColumns.TryGetValue(table_id, out loc10))
 | 
			
		||||
                    indexColumns.Add(table_id, loc10 = new Dictionary<string, List<DbColumnInfo>>());
 | 
			
		||||
                if (!loc10.TryGetValue(index_id, out loc11))
 | 
			
		||||
                    loc10.Add(index_id, loc11 = new List<DbColumnInfo>());
 | 
			
		||||
                loc11.Add(loc9);
 | 
			
		||||
                if (is_unique && !is_primary_key)
 | 
			
		||||
                {
 | 
			
		||||
                    if (!uniqueColumns.TryGetValue(table_id, out loc10))
 | 
			
		||||
                        uniqueColumns.Add(table_id, loc10 = new Dictionary<string, List<DbColumnInfo>>());
 | 
			
		||||
                    if (!loc10.TryGetValue(index_id, out loc11))
 | 
			
		||||
                        loc10.Add(index_id, loc11 = new List<DbColumnInfo>());
 | 
			
		||||
                    loc11.Add(loc9);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            foreach (string table_id in indexColumns.Keys)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var column in indexColumns[table_id])
 | 
			
		||||
                    loc2[table_id].IndexesDict.Add(column.Key, column.Value);
 | 
			
		||||
            }
 | 
			
		||||
            foreach (string table_id in uniqueColumns.Keys)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var column in uniqueColumns[table_id])
 | 
			
		||||
                {
 | 
			
		||||
                    column.Value.Sort((c1, c2) => c1.Name.CompareTo(c2.Name));
 | 
			
		||||
                    loc2[table_id].UniquesDict.Add(column.Key, column.Value);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            sql = string.Format(@"
 | 
			
		||||
select 
 | 
			
		||||
concat(a.constraint_schema, '.', a.table_name) 'table_id',
 | 
			
		||||
a.column_name,
 | 
			
		||||
a.constraint_name 'FKId',
 | 
			
		||||
concat(a.referenced_table_schema, '.', a.referenced_table_name) 'ref_table_id',
 | 
			
		||||
1 'IsForeignKey',
 | 
			
		||||
a.referenced_column_name 'ref_column'
 | 
			
		||||
from information_schema.key_column_usage a
 | 
			
		||||
where a.constraint_schema in ({1}) and a.table_name in ({0}) and not isnull(position_in_unique_constraint)
 | 
			
		||||
", loc8, databaseIn);
 | 
			
		||||
            ds = _orm.Ado.ExecuteArray(CommandType.Text, sql);
 | 
			
		||||
            if (ds == null) return loc1;
 | 
			
		||||
 | 
			
		||||
            var fkColumns = new Dictionary<string, Dictionary<string, DbForeignInfo>>();
 | 
			
		||||
            foreach (var row in ds)
 | 
			
		||||
            {
 | 
			
		||||
                string table_id = string.Concat(row[0]);
 | 
			
		||||
                string column = string.Concat(row[1]);
 | 
			
		||||
                string fk_id = string.Concat(row[2]);
 | 
			
		||||
                string ref_table_id = string.Concat(row[3]);
 | 
			
		||||
                bool is_foreign_key = string.Concat(row[4]) == "1";
 | 
			
		||||
                string referenced_column = string.Concat(row[5]);
 | 
			
		||||
                if (database.Length == 1)
 | 
			
		||||
                {
 | 
			
		||||
                    table_id = table_id.Substring(table_id.IndexOf('.') + 1);
 | 
			
		||||
                    ref_table_id = ref_table_id.Substring(ref_table_id.IndexOf('.') + 1);
 | 
			
		||||
                }
 | 
			
		||||
                if (loc3.ContainsKey(table_id) == false || loc3[table_id].ContainsKey(column) == false) continue;
 | 
			
		||||
                var loc9 = loc3[table_id][column];
 | 
			
		||||
                if (loc2.ContainsKey(ref_table_id) == false) continue;
 | 
			
		||||
                var loc10 = loc2[ref_table_id];
 | 
			
		||||
                var loc11 = loc3[ref_table_id][referenced_column];
 | 
			
		||||
 | 
			
		||||
                Dictionary<string, DbForeignInfo> loc12 = null;
 | 
			
		||||
                DbForeignInfo loc13 = null;
 | 
			
		||||
                if (!fkColumns.TryGetValue(table_id, out loc12))
 | 
			
		||||
                    fkColumns.Add(table_id, loc12 = new Dictionary<string, DbForeignInfo>());
 | 
			
		||||
                if (!loc12.TryGetValue(fk_id, out loc13))
 | 
			
		||||
                    loc12.Add(fk_id, loc13 = new DbForeignInfo { Table = loc2[table_id], ReferencedTable = loc10 });
 | 
			
		||||
                loc13.Columns.Add(loc9);
 | 
			
		||||
                loc13.ReferencedColumns.Add(loc11);
 | 
			
		||||
            }
 | 
			
		||||
            foreach (var table_id in fkColumns.Keys)
 | 
			
		||||
                foreach (var fk in fkColumns[table_id])
 | 
			
		||||
                    loc2[table_id].ForeignsDict.Add(fk.Key, fk.Value);
 | 
			
		||||
 | 
			
		||||
            foreach (var table_id in loc3.Keys)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var loc5 in loc3[table_id].Values)
 | 
			
		||||
                {
 | 
			
		||||
                    loc2[table_id].Columns.Add(loc5);
 | 
			
		||||
                    if (loc5.IsIdentity) loc2[table_id].Identitys.Add(loc5);
 | 
			
		||||
                    if (loc5.IsPrimary) loc2[table_id].Primarys.Add(loc5);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            foreach (var loc4 in loc2.Values)
 | 
			
		||||
            {
 | 
			
		||||
                if (loc4.Primarys.Count == 0 && loc4.UniquesDict.Count > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    foreach (var loc5 in loc4.UniquesDict.First().Value)
 | 
			
		||||
                    {
 | 
			
		||||
                        loc5.IsPrimary = true;
 | 
			
		||||
                        loc4.Primarys.Add(loc5);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                loc4.Primarys.Sort((c1, c2) => c1.Name.CompareTo(c2.Name));
 | 
			
		||||
                loc4.Columns.Sort((c1, c2) =>
 | 
			
		||||
                {
 | 
			
		||||
                    int compare = c2.IsPrimary.CompareTo(c1.IsPrimary);
 | 
			
		||||
                    if (compare == 0)
 | 
			
		||||
                    {
 | 
			
		||||
                        bool b1 = loc4.ForeignsDict.Values.Where(fk => fk.Columns.Where(c3 => c3.Name == c1.Name).Any()).Any();
 | 
			
		||||
                        bool b2 = loc4.ForeignsDict.Values.Where(fk => fk.Columns.Where(c3 => c3.Name == c2.Name).Any()).Any();
 | 
			
		||||
                        compare = b2.CompareTo(b1);
 | 
			
		||||
                    }
 | 
			
		||||
                    if (compare == 0) compare = c1.Name.CompareTo(c2.Name);
 | 
			
		||||
                    return compare;
 | 
			
		||||
                });
 | 
			
		||||
                loc1.Add(loc4);
 | 
			
		||||
            }
 | 
			
		||||
            loc1.Sort((t1, t2) =>
 | 
			
		||||
            {
 | 
			
		||||
                var ret = t1.Schema.CompareTo(t2.Schema);
 | 
			
		||||
                if (ret == 0) ret = t1.Name.CompareTo(t2.Name);
 | 
			
		||||
                return ret;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            loc2.Clear();
 | 
			
		||||
            loc3.Clear();
 | 
			
		||||
            return loc1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public List<DbEnumInfo> GetEnumsByDatabase(params string[] database)
 | 
			
		||||
        {
 | 
			
		||||
            return new List<DbEnumInfo>();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										456
									
								
								Providers/FreeSql.Provider.Odbc/MySql/OdbcMySqlExpression.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										456
									
								
								Providers/FreeSql.Provider.Odbc/MySql/OdbcMySqlExpression.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,456 @@
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using FreeSql.Internal.Model;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Linq.Expressions;
 | 
			
		||||
using System.Text;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.MySql
 | 
			
		||||
{
 | 
			
		||||
    class OdbcMySqlExpression : CommonExpression
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        public OdbcMySqlExpression(CommonUtils common) : base(common) { }
 | 
			
		||||
 | 
			
		||||
        public override string ExpressionLambdaToSqlOther(Expression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            Func<Expression, string> getExp = exparg => ExpressionLambdaToSql(exparg, tsc);
 | 
			
		||||
            switch (exp.NodeType)
 | 
			
		||||
            {
 | 
			
		||||
                case ExpressionType.Convert:
 | 
			
		||||
                    var operandExp = (exp as UnaryExpression)?.Operand;
 | 
			
		||||
                    var gentype = exp.Type.NullableTypeOrThis();
 | 
			
		||||
                    if (gentype != operandExp.Type.NullableTypeOrThis())
 | 
			
		||||
                    {
 | 
			
		||||
                        switch (gentype.ToString())
 | 
			
		||||
                        {
 | 
			
		||||
                            case "System.Boolean": return $"({getExp(operandExp)} not in ('0','false'))";
 | 
			
		||||
                            case "System.Byte": return $"cast({getExp(operandExp)} as unsigned)";
 | 
			
		||||
                            case "System.Char": return $"substr(cast({getExp(operandExp)} as char), 1, 1)";
 | 
			
		||||
                            case "System.DateTime": return $"cast({getExp(operandExp)} as datetime)";
 | 
			
		||||
                            case "System.Decimal": return $"cast({getExp(operandExp)} as decimal(36,18))";
 | 
			
		||||
                            case "System.Double": return $"cast({getExp(operandExp)} as decimal(32,16))";
 | 
			
		||||
                            case "System.Int16":
 | 
			
		||||
                            case "System.Int32":
 | 
			
		||||
                            case "System.Int64":
 | 
			
		||||
                            case "System.SByte": return $"cast({getExp(operandExp)} as signed)";
 | 
			
		||||
                            case "System.Single": return $"cast({getExp(operandExp)} as decimal(14,7))";
 | 
			
		||||
                            case "System.String": return $"cast({getExp(operandExp)} as char)";
 | 
			
		||||
                            case "System.UInt16":
 | 
			
		||||
                            case "System.UInt32":
 | 
			
		||||
                            case "System.UInt64": return $"cast({getExp(operandExp)} as unsigned)";
 | 
			
		||||
                            case "System.Guid": return $"substr(cast({getExp(operandExp)} as char), 1, 36)";
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
                case ExpressionType.Call:
 | 
			
		||||
                    var callExp = exp as MethodCallExpression;
 | 
			
		||||
 | 
			
		||||
                    switch (callExp.Method.Name)
 | 
			
		||||
                    {
 | 
			
		||||
                        case "Parse":
 | 
			
		||||
                        case "TryParse":
 | 
			
		||||
                            switch (callExp.Method.DeclaringType.NullableTypeOrThis().ToString())
 | 
			
		||||
                            {
 | 
			
		||||
                                case "System.Boolean": return $"({getExp(callExp.Arguments[0])} not in ('0','false'))";
 | 
			
		||||
                                case "System.Byte": return $"cast({getExp(callExp.Arguments[0])} as unsigned)";
 | 
			
		||||
                                case "System.Char": return $"substr(cast({getExp(callExp.Arguments[0])} as char), 1, 1)";
 | 
			
		||||
                                case "System.DateTime": return $"cast({getExp(callExp.Arguments[0])} as datetime)";
 | 
			
		||||
                                case "System.Decimal": return $"cast({getExp(callExp.Arguments[0])} as decimal(36,18))";
 | 
			
		||||
                                case "System.Double": return $"cast({getExp(callExp.Arguments[0])} as decimal(32,16))";
 | 
			
		||||
                                case "System.Int16":
 | 
			
		||||
                                case "System.Int32":
 | 
			
		||||
                                case "System.Int64":
 | 
			
		||||
                                case "System.SByte": return $"cast({getExp(callExp.Arguments[0])} as signed)";
 | 
			
		||||
                                case "System.Single": return $"cast({getExp(callExp.Arguments[0])} as decimal(14,7))";
 | 
			
		||||
                                case "System.UInt16":
 | 
			
		||||
                                case "System.UInt32":
 | 
			
		||||
                                case "System.UInt64": return $"cast({getExp(callExp.Arguments[0])} as unsigned)";
 | 
			
		||||
                                case "System.Guid": return $"substr(cast({getExp(callExp.Arguments[0])} as char), 1, 36)";
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "NewGuid":
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "Next":
 | 
			
		||||
                            if (callExp.Object?.Type == typeof(Random)) return "cast(rand()*1000000000 as signed)";
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "NextDouble":
 | 
			
		||||
                            if (callExp.Object?.Type == typeof(Random)) return "rand()";
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "Random":
 | 
			
		||||
                            if (callExp.Method.DeclaringType.IsNumberType()) return "rand()";
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "ToString":
 | 
			
		||||
                            if (callExp.Object != null) return callExp.Arguments.Count == 0 ? $"cast({getExp(callExp.Object)} as char)" : null;
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var objExp = callExp.Object;
 | 
			
		||||
                    var objType = objExp?.Type;
 | 
			
		||||
                    if (objType?.FullName == "System.Byte[]") return null;
 | 
			
		||||
 | 
			
		||||
                    var argIndex = 0;
 | 
			
		||||
                    if (objType == null && callExp.Method.DeclaringType == typeof(Enumerable))
 | 
			
		||||
                    {
 | 
			
		||||
                        objExp = callExp.Arguments.FirstOrDefault();
 | 
			
		||||
                        objType = objExp?.Type;
 | 
			
		||||
                        argIndex++;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (objType == null) objType = callExp.Method.DeclaringType;
 | 
			
		||||
                    if (objType != null || objType.IsArray || typeof(IList).IsAssignableFrom(callExp.Method.DeclaringType))
 | 
			
		||||
                    {
 | 
			
		||||
                        var left = objExp == null ? null : getExp(objExp);
 | 
			
		||||
                        switch (callExp.Method.Name)
 | 
			
		||||
                        {
 | 
			
		||||
                            case "Contains":
 | 
			
		||||
                                //判断 in
 | 
			
		||||
                                return $"({getExp(callExp.Arguments[argIndex])}) in {left}";
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
                case ExpressionType.NewArrayInit:
 | 
			
		||||
                    var arrExp = exp as NewArrayExpression;
 | 
			
		||||
                    var arrSb = new StringBuilder();
 | 
			
		||||
                    arrSb.Append("(");
 | 
			
		||||
                    for (var a = 0; a < arrExp.Expressions.Count; a++)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (a > 0) arrSb.Append(",");
 | 
			
		||||
                        arrSb.Append(getExp(arrExp.Expressions[a]));
 | 
			
		||||
                    }
 | 
			
		||||
                    if (arrSb.Length == 1) arrSb.Append("NULL");
 | 
			
		||||
                    return arrSb.Append(")").ToString();
 | 
			
		||||
                case ExpressionType.ListInit:
 | 
			
		||||
                    var listExp = exp as ListInitExpression;
 | 
			
		||||
                    var listSb = new StringBuilder();
 | 
			
		||||
                    listSb.Append("(");
 | 
			
		||||
                    for (var a = 0; a < listExp.Initializers.Count; a++)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (listExp.Initializers[a].Arguments.Any() == false) continue;
 | 
			
		||||
                        if (a > 0) listSb.Append(",");
 | 
			
		||||
                        listSb.Append(getExp(listExp.Initializers[a].Arguments.FirstOrDefault()));
 | 
			
		||||
                    }
 | 
			
		||||
                    if (listSb.Length == 1) listSb.Append("NULL");
 | 
			
		||||
                    return listSb.Append(")").ToString();
 | 
			
		||||
                case ExpressionType.New:
 | 
			
		||||
                    var newExp = exp as NewExpression;
 | 
			
		||||
                    if (typeof(IList).IsAssignableFrom(newExp.Type))
 | 
			
		||||
                    {
 | 
			
		||||
                        if (newExp.Arguments.Count == 0) return "(NULL)";
 | 
			
		||||
                        if (typeof(IEnumerable).IsAssignableFrom(newExp.Arguments[0].Type) == false) return "(NULL)";
 | 
			
		||||
                        return getExp(newExp.Arguments[0]);
 | 
			
		||||
                    }
 | 
			
		||||
                    return null;
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override string ExpressionLambdaToSqlMemberAccessString(MemberExpression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            if (exp.Expression == null)
 | 
			
		||||
            {
 | 
			
		||||
                switch (exp.Member.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "Empty": return "''";
 | 
			
		||||
                }
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            var left = ExpressionLambdaToSql(exp.Expression, tsc);
 | 
			
		||||
            switch (exp.Member.Name)
 | 
			
		||||
            {
 | 
			
		||||
                case "Length": return $"char_length({left})";
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        public override string ExpressionLambdaToSqlMemberAccessDateTime(MemberExpression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            if (exp.Expression == null)
 | 
			
		||||
            {
 | 
			
		||||
                switch (exp.Member.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "Now": return "now()";
 | 
			
		||||
                    case "UtcNow": return "utc_timestamp()";
 | 
			
		||||
                    case "Today": return "curdate()";
 | 
			
		||||
                    case "MinValue": return "cast('0001/1/1 0:00:00' as datetime)";
 | 
			
		||||
                    case "MaxValue": return "cast('9999/12/31 23:59:59' as datetime)";
 | 
			
		||||
                }
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            var left = ExpressionLambdaToSql(exp.Expression, tsc);
 | 
			
		||||
            switch (exp.Member.Name)
 | 
			
		||||
            {
 | 
			
		||||
                case "Date": return $"cast(date_format({left},'%Y-%m-%d') as datetime)";
 | 
			
		||||
                case "TimeOfDay": return $"timestampdiff(microsecond, date_format({left},'%Y-%m-%d'), {left})";
 | 
			
		||||
                case "DayOfWeek": return $"(dayofweek({left})-1)";
 | 
			
		||||
                case "Day": return $"dayofmonth({left})";
 | 
			
		||||
                case "DayOfYear": return $"dayofyear({left})";
 | 
			
		||||
                case "Month": return $"month({left})";
 | 
			
		||||
                case "Year": return $"year({left})";
 | 
			
		||||
                case "Hour": return $"hour({left})";
 | 
			
		||||
                case "Minute": return $"minute({left})";
 | 
			
		||||
                case "Second": return $"second({left})";
 | 
			
		||||
                case "Millisecond": return $"floor(microsecond({left})/1000)";
 | 
			
		||||
                case "Ticks": return $"(timestampdiff(microsecond, '0001-1-1', {left})*10)";
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        public override string ExpressionLambdaToSqlMemberAccessTimeSpan(MemberExpression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            if (exp.Expression == null)
 | 
			
		||||
            {
 | 
			
		||||
                switch (exp.Member.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "Zero": return "0";
 | 
			
		||||
                    case "MinValue": return "-922337203685477580"; //微秒 Ticks / 10
 | 
			
		||||
                    case "MaxValue": return "922337203685477580";
 | 
			
		||||
                }
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            var left = ExpressionLambdaToSql(exp.Expression, tsc);
 | 
			
		||||
            switch (exp.Member.Name)
 | 
			
		||||
            {
 | 
			
		||||
                case "Days": return $"(({left}) div {(long)1000000 * 60 * 60 * 24})";
 | 
			
		||||
                case "Hours": return $"(({left}) div {(long)1000000 * 60 * 60} mod 24)";
 | 
			
		||||
                case "Milliseconds": return $"(({left}) div 1000 mod 1000)";
 | 
			
		||||
                case "Minutes": return $"(({left}) div {(long)1000000 * 60} mod 60)";
 | 
			
		||||
                case "Seconds": return $"(({left}) div 1000000 mod 60)";
 | 
			
		||||
                case "Ticks": return $"(({left}) * 10)";
 | 
			
		||||
                case "TotalDays": return $"(({left}) / {(long)1000000 * 60 * 60 * 24})";
 | 
			
		||||
                case "TotalHours": return $"(({left}) / {(long)1000000 * 60 * 60})";
 | 
			
		||||
                case "TotalMilliseconds": return $"(({left}) / 1000)";
 | 
			
		||||
                case "TotalMinutes": return $"(({left}) / {(long)1000000 * 60})";
 | 
			
		||||
                case "TotalSeconds": return $"(({left}) / 1000000)";
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override string ExpressionLambdaToSqlCallString(MethodCallExpression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            Func<Expression, string> getExp = exparg => ExpressionLambdaToSql(exparg, tsc);
 | 
			
		||||
            if (exp.Object == null)
 | 
			
		||||
            {
 | 
			
		||||
                switch (exp.Method.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "IsNullOrEmpty":
 | 
			
		||||
                        var arg1 = getExp(exp.Arguments[0]);
 | 
			
		||||
                        return $"({arg1} is null or {arg1} = '')";
 | 
			
		||||
                    case "IsNullOrWhiteSpace":
 | 
			
		||||
                        var arg2 = getExp(exp.Arguments[0]);
 | 
			
		||||
                        return $"({arg2} is null or {arg2} = '' or ltrim({arg2}) = '')";
 | 
			
		||||
                    case "Concat":
 | 
			
		||||
                        return _common.StringConcat(exp.Arguments.Select(a => getExp(a)).ToArray(), null);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                var left = getExp(exp.Object);
 | 
			
		||||
                switch (exp.Method.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "StartsWith":
 | 
			
		||||
                    case "EndsWith":
 | 
			
		||||
                    case "Contains":
 | 
			
		||||
                        var args0Value = getExp(exp.Arguments[0]);
 | 
			
		||||
                        if (args0Value == "NULL") return $"({left}) IS NULL";
 | 
			
		||||
                        if (exp.Method.Name == "StartsWith") return $"({left}) LIKE {(args0Value.EndsWith("'") ? args0Value.Insert(args0Value.Length - 1, "%") : $"concat({args0Value}, '%')")}";
 | 
			
		||||
                        if (exp.Method.Name == "EndsWith") return $"({left}) LIKE {(args0Value.StartsWith("'") ? args0Value.Insert(1, "%") : $"concat('%', {args0Value})")}";
 | 
			
		||||
                        if (args0Value.StartsWith("'") && args0Value.EndsWith("'")) return $"({left}) LIKE {args0Value.Insert(1, "%").Insert(args0Value.Length, "%")}";
 | 
			
		||||
                        return $"({left}) LIKE concat('%', {args0Value}, '%')";
 | 
			
		||||
                    case "ToLower": return $"lower({left})";
 | 
			
		||||
                    case "ToUpper": return $"upper({left})";
 | 
			
		||||
                    case "Substring":
 | 
			
		||||
                        var substrArgs1 = getExp(exp.Arguments[0]);
 | 
			
		||||
                        if (long.TryParse(substrArgs1, out var testtrylng1)) substrArgs1 = (testtrylng1 + 1).ToString();
 | 
			
		||||
                        else substrArgs1 += "+1";
 | 
			
		||||
                        if (exp.Arguments.Count == 1) return $"substr({left}, {substrArgs1})";
 | 
			
		||||
                        return $"substr({left}, {substrArgs1}, {getExp(exp.Arguments[1])})";
 | 
			
		||||
                    case "IndexOf":
 | 
			
		||||
                        var indexOfFindStr = getExp(exp.Arguments[0]);
 | 
			
		||||
                        if (exp.Arguments.Count > 1 && exp.Arguments[1].Type.FullName == "System.Int32")
 | 
			
		||||
                        {
 | 
			
		||||
                            var locateArgs1 = getExp(exp.Arguments[1]);
 | 
			
		||||
                            if (long.TryParse(locateArgs1, out var testtrylng2)) locateArgs1 = (testtrylng2 + 1).ToString();
 | 
			
		||||
                            else locateArgs1 += "+1";
 | 
			
		||||
                            return $"(locate({left}, {indexOfFindStr}, {locateArgs1})-1)";
 | 
			
		||||
                        }
 | 
			
		||||
                        return $"(locate({left}, {indexOfFindStr})-1)";
 | 
			
		||||
                    case "PadLeft":
 | 
			
		||||
                        if (exp.Arguments.Count == 1) return $"lpad({left}, {getExp(exp.Arguments[0])})";
 | 
			
		||||
                        return $"lpad({left}, {getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})";
 | 
			
		||||
                    case "PadRight":
 | 
			
		||||
                        if (exp.Arguments.Count == 1) return $"rpad({left}, {getExp(exp.Arguments[0])})";
 | 
			
		||||
                        return $"rpad({left}, {getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})";
 | 
			
		||||
                    case "Trim":
 | 
			
		||||
                    case "TrimStart":
 | 
			
		||||
                    case "TrimEnd":
 | 
			
		||||
                        if (exp.Arguments.Count == 0)
 | 
			
		||||
                        {
 | 
			
		||||
                            if (exp.Method.Name == "Trim") return $"trim({left})";
 | 
			
		||||
                            if (exp.Method.Name == "TrimStart") return $"ltrim({left})";
 | 
			
		||||
                            if (exp.Method.Name == "TrimEnd") return $"rtrim({left})";
 | 
			
		||||
                        }
 | 
			
		||||
                        foreach (var argsTrim02 in exp.Arguments)
 | 
			
		||||
                        {
 | 
			
		||||
                            var argsTrim01s = new[] { argsTrim02 };
 | 
			
		||||
                            if (argsTrim02.NodeType == ExpressionType.NewArrayInit)
 | 
			
		||||
                            {
 | 
			
		||||
                                var arritem = argsTrim02 as NewArrayExpression;
 | 
			
		||||
                                argsTrim01s = arritem.Expressions.ToArray();
 | 
			
		||||
                            }
 | 
			
		||||
                            foreach (var argsTrim01 in argsTrim01s)
 | 
			
		||||
                            {
 | 
			
		||||
                                if (exp.Method.Name == "Trim") left = $"trim({getExp(argsTrim01)} from {left})";
 | 
			
		||||
                                if (exp.Method.Name == "TrimStart") left = $"trim(leading {getExp(argsTrim01)} from {left})";
 | 
			
		||||
                                if (exp.Method.Name == "TrimEnd") left = $"trim(trailing {getExp(argsTrim01)} from {left})";
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        return left;
 | 
			
		||||
                    case "Replace": return $"replace({left}, {getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})";
 | 
			
		||||
                    case "CompareTo": return $"strcmp({left}, {getExp(exp.Arguments[0])})";
 | 
			
		||||
                    case "Equals": return $"({left} = {getExp(exp.Arguments[0])})";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        public override string ExpressionLambdaToSqlCallMath(MethodCallExpression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            Func<Expression, string> getExp = exparg => ExpressionLambdaToSql(exparg, tsc);
 | 
			
		||||
            switch (exp.Method.Name)
 | 
			
		||||
            {
 | 
			
		||||
                case "Abs": return $"abs({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Sign": return $"sign({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Floor": return $"floor({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Ceiling": return $"ceiling({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Round":
 | 
			
		||||
                    if (exp.Arguments.Count > 1 && exp.Arguments[1].Type.FullName == "System.Int32") return $"round({getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})";
 | 
			
		||||
                    return $"round({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Exp": return $"exp({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Log": return $"log({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Log10": return $"log10({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Pow": return $"pow({getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})";
 | 
			
		||||
                case "Sqrt": return $"sqrt({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Cos": return $"cos({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Sin": return $"sin({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Tan": return $"tan({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Acos": return $"acos({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Asin": return $"asin({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Atan": return $"atan({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Atan2": return $"atan2({getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})";
 | 
			
		||||
                case "Truncate": return $"truncate({getExp(exp.Arguments[0])}, 0)";
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        public override string ExpressionLambdaToSqlCallDateTime(MethodCallExpression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            Func<Expression, string> getExp = exparg => ExpressionLambdaToSql(exparg, tsc);
 | 
			
		||||
            if (exp.Object == null)
 | 
			
		||||
            {
 | 
			
		||||
                switch (exp.Method.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "Compare": return $"({getExp(exp.Arguments[0])} - ({getExp(exp.Arguments[1])}))";
 | 
			
		||||
                    case "DaysInMonth": return $"dayofmonth(last_day(concat({getExp(exp.Arguments[0])}, '-', {getExp(exp.Arguments[1])}, '-01')))";
 | 
			
		||||
                    case "Equals": return $"({getExp(exp.Arguments[0])} = {getExp(exp.Arguments[1])})";
 | 
			
		||||
 | 
			
		||||
                    case "IsLeapYear":
 | 
			
		||||
                        var isLeapYearArgs1 = getExp(exp.Arguments[0]);
 | 
			
		||||
                        return $"(({isLeapYearArgs1})%4=0 AND ({isLeapYearArgs1})%100<>0 OR ({isLeapYearArgs1})%400=0)";
 | 
			
		||||
 | 
			
		||||
                    case "Parse": return $"cast({getExp(exp.Arguments[0])} as datetime)";
 | 
			
		||||
                    case "ParseExact":
 | 
			
		||||
                    case "TryParse":
 | 
			
		||||
                    case "TryParseExact": return $"cast({getExp(exp.Arguments[0])} as datetime)";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                var left = getExp(exp.Object);
 | 
			
		||||
                var args1 = exp.Arguments.Count == 0 ? null : getExp(exp.Arguments[0]);
 | 
			
		||||
                switch (exp.Method.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "Add": return $"date_add({left}, interval ({args1}) microsecond)";
 | 
			
		||||
                    case "AddDays": return $"date_add({left}, interval ({args1}) day)";
 | 
			
		||||
                    case "AddHours": return $"date_add({left}, interval ({args1}) hour)";
 | 
			
		||||
                    case "AddMilliseconds": return $"date_add({left}, interval ({args1})*1000 microsecond)";
 | 
			
		||||
                    case "AddMinutes": return $"date_add({left}, interval ({args1}) minute)";
 | 
			
		||||
                    case "AddMonths": return $"date_add({left}, interval ({args1}) month)";
 | 
			
		||||
                    case "AddSeconds": return $"date_add({left}, interval ({args1}) second)";
 | 
			
		||||
                    case "AddTicks": return $"date_add({left}, interval ({args1})/10 microsecond)";
 | 
			
		||||
                    case "AddYears": return $"date_add({left}, interval ({args1}) year)";
 | 
			
		||||
                    case "Subtract":
 | 
			
		||||
                        switch ((exp.Arguments[0].Type.IsNullableType() ? exp.Arguments[0].Type.GenericTypeArguments.FirstOrDefault() : exp.Arguments[0].Type).FullName)
 | 
			
		||||
                        {
 | 
			
		||||
                            case "System.DateTime": return $"timestampdiff(microsecond, {args1}, {left})";
 | 
			
		||||
                            case "System.TimeSpan": return $"date_sub({left}, interval ({args1}) microsecond)";
 | 
			
		||||
                        }
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "Equals": return $"({left} = {getExp(exp.Arguments[0])})";
 | 
			
		||||
                    case "CompareTo": return $"timestampdiff(microsecond,{args1},{left})";
 | 
			
		||||
                    case "ToString": return exp.Arguments.Count == 0 ? $"date_format({left}, '%Y-%m-%d %H:%i:%s.%f')" : null;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        public override string ExpressionLambdaToSqlCallTimeSpan(MethodCallExpression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            Func<Expression, string> getExp = exparg => ExpressionLambdaToSql(exparg, tsc);
 | 
			
		||||
            if (exp.Object == null)
 | 
			
		||||
            {
 | 
			
		||||
                switch (exp.Method.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "Compare": return $"({getExp(exp.Arguments[0])}-({getExp(exp.Arguments[1])}))";
 | 
			
		||||
                    case "Equals": return $"({getExp(exp.Arguments[0])} = {getExp(exp.Arguments[1])})";
 | 
			
		||||
                    case "FromDays": return $"(({getExp(exp.Arguments[0])})*{(long)1000000 * 60 * 60 * 24})";
 | 
			
		||||
                    case "FromHours": return $"(({getExp(exp.Arguments[0])})*{(long)1000000 * 60 * 60})";
 | 
			
		||||
                    case "FromMilliseconds": return $"(({getExp(exp.Arguments[0])})*1000)";
 | 
			
		||||
                    case "FromMinutes": return $"(({getExp(exp.Arguments[0])})*{(long)1000000 * 60})";
 | 
			
		||||
                    case "FromSeconds": return $"(({getExp(exp.Arguments[0])})*1000000)";
 | 
			
		||||
                    case "FromTicks": return $"(({getExp(exp.Arguments[0])})/10)";
 | 
			
		||||
                    case "Parse": return $"cast({getExp(exp.Arguments[0])} as signed)";
 | 
			
		||||
                    case "ParseExact":
 | 
			
		||||
                    case "TryParse":
 | 
			
		||||
                    case "TryParseExact": return $"cast({getExp(exp.Arguments[0])} as signed)";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                var left = getExp(exp.Object);
 | 
			
		||||
                var args1 = exp.Arguments.Count == 0 ? null : getExp(exp.Arguments[0]);
 | 
			
		||||
                switch (exp.Method.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "Add": return $"({left}+{args1})";
 | 
			
		||||
                    case "Subtract": return $"({left}-({args1}))";
 | 
			
		||||
                    case "Equals": return $"({left} = {getExp(exp.Arguments[0])})";
 | 
			
		||||
                    case "CompareTo": return $"({left}-({getExp(exp.Arguments[0])}))";
 | 
			
		||||
                    case "ToString": return $"cast({left} as char)";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        public override string ExpressionLambdaToSqlCallConvert(MethodCallExpression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            Func<Expression, string> getExp = exparg => ExpressionLambdaToSql(exparg, tsc);
 | 
			
		||||
            if (exp.Object == null)
 | 
			
		||||
            {
 | 
			
		||||
                switch (exp.Method.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "ToBoolean": return $"({getExp(exp.Arguments[0])} not in ('0','false'))";
 | 
			
		||||
                    case "ToByte": return $"cast({getExp(exp.Arguments[0])} as unsigned)";
 | 
			
		||||
                    case "ToChar": return $"substr(cast({getExp(exp.Arguments[0])} as char), 1, 1)";
 | 
			
		||||
                    case "ToDateTime": return $"cast({getExp(exp.Arguments[0])} as datetime)";
 | 
			
		||||
                    case "ToDecimal": return $"cast({getExp(exp.Arguments[0])} as decimal(36,18))";
 | 
			
		||||
                    case "ToDouble": return $"cast({getExp(exp.Arguments[0])} as decimal(32,16))";
 | 
			
		||||
                    case "ToInt16":
 | 
			
		||||
                    case "ToInt32":
 | 
			
		||||
                    case "ToInt64":
 | 
			
		||||
                    case "ToSByte": return $"cast({getExp(exp.Arguments[0])} as signed)";
 | 
			
		||||
                    case "ToSingle": return $"cast({getExp(exp.Arguments[0])} as decimal(14,7))";
 | 
			
		||||
                    case "ToString": return $"cast({getExp(exp.Arguments[0])} as char)";
 | 
			
		||||
                    case "ToUInt16":
 | 
			
		||||
                    case "ToUInt32":
 | 
			
		||||
                    case "ToUInt64": return $"cast({getExp(exp.Arguments[0])} as unsigned)";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										63
									
								
								Providers/FreeSql.Provider.Odbc/MySql/OdbcMySqlProvider.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								Providers/FreeSql.Provider.Odbc/MySql/OdbcMySqlProvider.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using FreeSql.Internal.CommonProvider;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data.Common;
 | 
			
		||||
using System.Linq.Expressions;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.MySql
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    public class OdbcMySqlProvider<TMark> : IFreeSql<TMark>
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        static OdbcMySqlProvider()
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ISelect<T1> Select<T1>() where T1 : class => new OdbcMySqlSelect<T1>(this, this.InternalCommonUtils, this.InternalCommonExpression, null);
 | 
			
		||||
        public ISelect<T1> Select<T1>(object dywhere) where T1 : class => new OdbcMySqlSelect<T1>(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere);
 | 
			
		||||
        public IInsert<T1> Insert<T1>() where T1 : class => new OdbcMySqlInsert<T1>(this, this.InternalCommonUtils, this.InternalCommonExpression);
 | 
			
		||||
        public IInsert<T1> Insert<T1>(T1 source) where T1 : class => this.Insert<T1>().AppendData(source);
 | 
			
		||||
        public IInsert<T1> Insert<T1>(T1[] source) where T1 : class => this.Insert<T1>().AppendData(source);
 | 
			
		||||
        public IInsert<T1> Insert<T1>(IEnumerable<T1> source) where T1 : class => this.Insert<T1>().AppendData(source);
 | 
			
		||||
        public IUpdate<T1> Update<T1>() where T1 : class => new OdbcMySqlUpdate<T1>(this, this.InternalCommonUtils, this.InternalCommonExpression, null);
 | 
			
		||||
        public IUpdate<T1> Update<T1>(object dywhere) where T1 : class => new OdbcMySqlUpdate<T1>(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere);
 | 
			
		||||
        public IDelete<T1> Delete<T1>() where T1 : class => new OdbcMySqlDelete<T1>(this, this.InternalCommonUtils, this.InternalCommonExpression, null);
 | 
			
		||||
        public IDelete<T1> Delete<T1>(object dywhere) where T1 : class => new OdbcMySqlDelete<T1>(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere);
 | 
			
		||||
 | 
			
		||||
        public IAdo Ado { get; }
 | 
			
		||||
        public IAop Aop { get; }
 | 
			
		||||
        public ICodeFirst CodeFirst { get; }
 | 
			
		||||
        public IDbFirst DbFirst { get; }
 | 
			
		||||
        public OdbcMySqlProvider(string masterConnectionString, string[] slaveConnectionString)
 | 
			
		||||
        {
 | 
			
		||||
            this.InternalCommonUtils = new OdbcMySqlUtils(this);
 | 
			
		||||
            this.InternalCommonExpression = new OdbcMySqlExpression(this.InternalCommonUtils);
 | 
			
		||||
 | 
			
		||||
            this.Ado = new OdbcMySqlAdo(this.InternalCommonUtils, masterConnectionString, slaveConnectionString);
 | 
			
		||||
            this.Aop = new AopProvider();
 | 
			
		||||
 | 
			
		||||
            this.DbFirst = new OdbcMySqlDbFirst(this, this.InternalCommonUtils, this.InternalCommonExpression);
 | 
			
		||||
            this.CodeFirst = new OdbcMySqlCodeFirst(this, this.InternalCommonUtils, this.InternalCommonExpression);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        internal CommonUtils InternalCommonUtils { get; }
 | 
			
		||||
        internal CommonExpression InternalCommonExpression { get; }
 | 
			
		||||
 | 
			
		||||
        public void Transaction(Action handler) => Ado.Transaction(handler);
 | 
			
		||||
 | 
			
		||||
        public void Transaction(Action handler, TimeSpan timeout) => Ado.Transaction(handler, timeout);
 | 
			
		||||
 | 
			
		||||
        ~OdbcMySqlProvider()
 | 
			
		||||
        {
 | 
			
		||||
            this.Dispose();
 | 
			
		||||
        }
 | 
			
		||||
        bool _isdisposed = false;
 | 
			
		||||
        public void Dispose()
 | 
			
		||||
        {
 | 
			
		||||
            if (_isdisposed) return;
 | 
			
		||||
            (this.Ado as AdoProvider)?.Dispose();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										112
									
								
								Providers/FreeSql.Provider.Odbc/MySql/OdbcMySqlUtils.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								Providers/FreeSql.Provider.Odbc/MySql/OdbcMySqlUtils.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,112 @@
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using FreeSql.Internal.Model;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data.Common;
 | 
			
		||||
using System.Data.Odbc;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Linq.Expressions;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using System.Text;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.MySql
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    class OdbcMySqlUtils : CommonUtils
 | 
			
		||||
    {
 | 
			
		||||
        public OdbcMySqlUtils(IFreeSql orm) : base(orm)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override DbParameter AppendParamter(List<DbParameter> _params, string parameterName, Type type, object value)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrEmpty(parameterName)) parameterName = $"p_{_params?.Count}";
 | 
			
		||||
            var ret = new OdbcParameter { ParameterName = QuoteParamterName(parameterName), Value = value };
 | 
			
		||||
            var tp = _orm.CodeFirst.GetDbInfo(type)?.type;
 | 
			
		||||
            if (tp != null)
 | 
			
		||||
                ret.OdbcType = (OdbcType)tp.Value;
 | 
			
		||||
            _params?.Add(ret);
 | 
			
		||||
            return ret;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override DbParameter[] GetDbParamtersByObject(string sql, object obj) =>
 | 
			
		||||
            Utils.GetDbParamtersByObject<OdbcParameter>(sql, obj, null, (name, type, value) =>
 | 
			
		||||
            {
 | 
			
		||||
                var ret = new OdbcParameter { ParameterName = $"?{name}", Value = value };
 | 
			
		||||
                var tp = _orm.CodeFirst.GetDbInfo(type)?.type;
 | 
			
		||||
                if (tp != null)
 | 
			
		||||
                    ret.OdbcType = (OdbcType)tp.Value;
 | 
			
		||||
                return ret;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        public override string FormatSql(string sql, params object[] args) => sql?.FormatOdbcMySql(args);
 | 
			
		||||
        public override string QuoteSqlName(string name)
 | 
			
		||||
        {
 | 
			
		||||
            var nametrim = name.Trim();
 | 
			
		||||
            if (nametrim.StartsWith("(") && nametrim.EndsWith(")"))
 | 
			
		||||
                return nametrim; //原生SQL
 | 
			
		||||
            return $"`{nametrim.Trim('`').Replace(".", "`.`")}`";
 | 
			
		||||
        }
 | 
			
		||||
        public override string TrimQuoteSqlName(string name)
 | 
			
		||||
        {
 | 
			
		||||
            var nametrim = name.Trim();
 | 
			
		||||
            if (nametrim.StartsWith("(") && nametrim.EndsWith(")"))
 | 
			
		||||
                return nametrim; //原生SQL
 | 
			
		||||
            return $"{nametrim.Trim('`').Replace("`.`", ".").Replace(".`", ".")}";
 | 
			
		||||
        }
 | 
			
		||||
        public override string QuoteParamterName(string name) => $"?{(_orm.CodeFirst.IsSyncStructureToLower ? name.ToLower() : name)}";
 | 
			
		||||
        public override string IsNull(string sql, object value) => $"ifnull({sql}, {value})";
 | 
			
		||||
        public override string StringConcat(string[] objs, Type[] types) => $"concat({string.Join(", ", objs)})";
 | 
			
		||||
        public override string Mod(string left, string right, Type leftType, Type rightType) => $"{left} % {right}";
 | 
			
		||||
        public override string Div(string left, string right, Type leftType, Type rightType) => $"{left} div {right}";
 | 
			
		||||
 | 
			
		||||
        public override string QuoteWriteParamter(Type type, string paramterName)
 | 
			
		||||
        {
 | 
			
		||||
            switch (type.FullName)
 | 
			
		||||
            {
 | 
			
		||||
                case "MygisPoint":
 | 
			
		||||
                case "MygisLineString":
 | 
			
		||||
                case "MygisPolygon":
 | 
			
		||||
                case "MygisMultiPoint":
 | 
			
		||||
                case "MygisMultiLineString":
 | 
			
		||||
                case "MygisMultiPolygon": return $"ST_GeomFromText({paramterName})";
 | 
			
		||||
            }
 | 
			
		||||
            return paramterName;
 | 
			
		||||
        }
 | 
			
		||||
        public override string QuoteReadColumn(Type type, string columnName)
 | 
			
		||||
        {
 | 
			
		||||
            switch (type.FullName)
 | 
			
		||||
            {
 | 
			
		||||
                case "MygisPoint":
 | 
			
		||||
                case "MygisLineString":
 | 
			
		||||
                case "MygisPolygon":
 | 
			
		||||
                case "MygisMultiPoint":
 | 
			
		||||
                case "MygisMultiLineString":
 | 
			
		||||
                case "MygisMultiPolygon": return $"ST_AsText({columnName})";
 | 
			
		||||
            }
 | 
			
		||||
            return columnName;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override string GetNoneParamaterSqlValue(List<DbParameter> specialParams, Type type, object value)
 | 
			
		||||
        {
 | 
			
		||||
            if (value == null) return "NULL";
 | 
			
		||||
            if (type == typeof(byte[]))
 | 
			
		||||
            {
 | 
			
		||||
                var bytes = value as byte[];
 | 
			
		||||
                var sb = new StringBuilder().Append("0x");
 | 
			
		||||
                foreach (var vc in bytes)
 | 
			
		||||
                {
 | 
			
		||||
                    if (vc < 10) sb.Append("0");
 | 
			
		||||
                    sb.Append(vc.ToString("X"));
 | 
			
		||||
                }
 | 
			
		||||
                return sb.ToString(); //val = Encoding.UTF8.GetString(val as byte[]);
 | 
			
		||||
            }
 | 
			
		||||
            else if (type == typeof(TimeSpan) || type == typeof(TimeSpan?))
 | 
			
		||||
            {
 | 
			
		||||
                var ts = (TimeSpan)value;
 | 
			
		||||
                value = $"{Math.Floor(ts.TotalHours)}:{ts.Minutes}:{ts.Seconds}";
 | 
			
		||||
            }
 | 
			
		||||
            return FormatSql("{0}", value, 1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,27 @@
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.Oracle
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    class OdbcOracleDelete<T1> : Internal.CommonProvider.DeleteProvider<T1> where T1 : class
 | 
			
		||||
    {
 | 
			
		||||
        public OdbcOracleDelete(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere)
 | 
			
		||||
            : base(orm, commonUtils, commonExpression, dywhere)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override List<T1> ExecuteDeleted()
 | 
			
		||||
        {
 | 
			
		||||
            throw new NotImplementedException();
 | 
			
		||||
        }
 | 
			
		||||
        public override Task<List<T1>> ExecuteDeletedAsync()
 | 
			
		||||
        {
 | 
			
		||||
            throw new NotImplementedException();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										211
									
								
								Providers/FreeSql.Provider.Odbc/Oracle/Curd/OdbcOracleInsert.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								Providers/FreeSql.Provider.Odbc/Oracle/Curd/OdbcOracleInsert.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,211 @@
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using FreeSql.Internal.Model;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data;
 | 
			
		||||
using System.Data.Common;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.Oracle
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    class OdbcOracleInsert<T1> : Internal.CommonProvider.InsertProvider<T1> where T1 : class
 | 
			
		||||
    {
 | 
			
		||||
        public OdbcOracleInsert(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression)
 | 
			
		||||
            : base(orm, commonUtils, commonExpression)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override int ExecuteAffrows() => base.SplitExecuteAffrows(500, 999);
 | 
			
		||||
        public override Task<int> ExecuteAffrowsAsync() => base.SplitExecuteAffrowsAsync(500, 999);
 | 
			
		||||
        public override long ExecuteIdentity() => base.SplitExecuteIdentity(500, 999);
 | 
			
		||||
        public override Task<long> ExecuteIdentityAsync() => base.SplitExecuteIdentityAsync(500, 999);
 | 
			
		||||
        public override List<T1> ExecuteInserted() => base.SplitExecuteInserted(500, 999);
 | 
			
		||||
        public override Task<List<T1>> ExecuteInsertedAsync() => base.SplitExecuteInsertedAsync(500, 999);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        public override string ToSql()
 | 
			
		||||
        {
 | 
			
		||||
            if (_source == null || _source.Any() == false) return null;
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            sb.Append("INSERT ");
 | 
			
		||||
            if (_source.Count > 1) sb.Append("ALL");
 | 
			
		||||
 | 
			
		||||
            _identCol = null;
 | 
			
		||||
            var sbtb = new StringBuilder();
 | 
			
		||||
            sbtb.Append("INTO ");
 | 
			
		||||
            sbtb.Append(_commonUtils.QuoteSqlName(TableRuleInvoke())).Append("(");
 | 
			
		||||
            var colidx = 0;
 | 
			
		||||
            foreach (var col in _table.Columns.Values)
 | 
			
		||||
            {
 | 
			
		||||
                if (col.Attribute.IsIdentity) _identCol = col;
 | 
			
		||||
                if (_ignore.ContainsKey(col.Attribute.Name)) continue;
 | 
			
		||||
                if (col.Attribute.IsIdentity && _insertIdentity == false) continue;
 | 
			
		||||
 | 
			
		||||
                if (colidx > 0) sbtb.Append(", ");
 | 
			
		||||
                sbtb.Append(_commonUtils.QuoteSqlName(col.Attribute.Name));
 | 
			
		||||
                ++colidx;
 | 
			
		||||
            }
 | 
			
		||||
            sbtb.Append(") ");
 | 
			
		||||
 | 
			
		||||
            _params = _noneParameter ? new DbParameter[0] : new DbParameter[colidx * _source.Count];
 | 
			
		||||
            var specialParams = new List<DbParameter>();
 | 
			
		||||
            var didx = 0;
 | 
			
		||||
            foreach (var d in _source)
 | 
			
		||||
            {
 | 
			
		||||
                if (_source.Count > 1) sb.Append("\r\n");
 | 
			
		||||
                sb.Append(sbtb);
 | 
			
		||||
                sb.Append("VALUES");
 | 
			
		||||
                sb.Append("(");
 | 
			
		||||
                var colidx2 = 0;
 | 
			
		||||
                foreach (var col in _table.Columns.Values)
 | 
			
		||||
                {
 | 
			
		||||
                    if (_ignore.ContainsKey(col.Attribute.Name)) continue;
 | 
			
		||||
                    if (col.Attribute.IsIdentity && _insertIdentity == false) continue;
 | 
			
		||||
 | 
			
		||||
                    if (colidx2 > 0) sb.Append(", ");
 | 
			
		||||
                    object val = col.GetMapValue(d);
 | 
			
		||||
                    if (_noneParameter)
 | 
			
		||||
                        sb.Append(_commonUtils.GetNoneParamaterSqlValue(specialParams, col.Attribute.MapType, val));
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        sb.Append(_commonUtils.QuoteWriteParamter(col.Attribute.MapType, _commonUtils.QuoteParamterName($"{col.CsName}_{didx}")));
 | 
			
		||||
                        _params[didx * colidx + colidx2] = _commonUtils.AppendParamter(null, $"{col.CsName}_{didx}", col.Attribute.MapType, val);
 | 
			
		||||
                    }
 | 
			
		||||
                    ++colidx2;
 | 
			
		||||
                }
 | 
			
		||||
                sb.Append(")");
 | 
			
		||||
                ++didx;
 | 
			
		||||
            }
 | 
			
		||||
            if (_source.Count > 1) sb.Append("\r\n SELECT 1 FROM DUAL");
 | 
			
		||||
            return sb.ToString();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ColumnInfo _identCol;
 | 
			
		||||
        protected override long RawExecuteIdentity()
 | 
			
		||||
        {
 | 
			
		||||
            var sql = this.ToSql();
 | 
			
		||||
            if (string.IsNullOrEmpty(sql)) return 0;
 | 
			
		||||
 | 
			
		||||
            long ret = 0;
 | 
			
		||||
            Exception exception = null;
 | 
			
		||||
            Aop.CurdBeforeEventArgs before = null;
 | 
			
		||||
 | 
			
		||||
            if (_identCol == null || _source.Count > 1)
 | 
			
		||||
            {
 | 
			
		||||
                before = new Aop.CurdBeforeEventArgs(_table.Type, Aop.CurdType.Insert, sql, _params);
 | 
			
		||||
                _orm.Aop.CurdBefore?.Invoke(this, before);
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    ret = _orm.Ado.ExecuteNonQuery(_connection, _transaction, CommandType.Text, sql, _params);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    exception = ex;
 | 
			
		||||
                    throw ex;
 | 
			
		||||
                }
 | 
			
		||||
                finally
 | 
			
		||||
                {
 | 
			
		||||
                    var after = new Aop.CurdAfterEventArgs(before, exception, ret);
 | 
			
		||||
                    _orm.Aop.CurdAfter?.Invoke(this, after);
 | 
			
		||||
                }
 | 
			
		||||
                return 0;
 | 
			
		||||
            }
 | 
			
		||||
            var identColName = _commonUtils.QuoteSqlName(_identCol.Attribute.Name);
 | 
			
		||||
            var identParam = _commonUtils.AppendParamter(null, $"{_identCol.CsName}99", _identCol.Attribute.MapType, 0);
 | 
			
		||||
            identParam.Direction = ParameterDirection.Output;
 | 
			
		||||
            sql = $"{sql} RETURNING {identColName} INTO {identParam.ParameterName}";
 | 
			
		||||
            var dbParms = _params.Concat(new[] { identParam }).ToArray();
 | 
			
		||||
            before = new Aop.CurdBeforeEventArgs(_table.Type, Aop.CurdType.Insert, sql, dbParms);
 | 
			
		||||
            _orm.Aop.CurdBefore?.Invoke(this, before);
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                _orm.Ado.ExecuteNonQuery(_connection, _transaction, CommandType.Text, sql, dbParms);
 | 
			
		||||
                long.TryParse(string.Concat(identParam.Value), out ret);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                exception = ex;
 | 
			
		||||
                throw ex;
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                var after = new Aop.CurdAfterEventArgs(before, exception, ret);
 | 
			
		||||
                _orm.Aop.CurdAfter?.Invoke(this, after);
 | 
			
		||||
            }
 | 
			
		||||
            return ret;
 | 
			
		||||
        }
 | 
			
		||||
        async protected override Task<long> RawExecuteIdentityAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var sql = this.ToSql();
 | 
			
		||||
            if (string.IsNullOrEmpty(sql)) return 0;
 | 
			
		||||
 | 
			
		||||
            long ret = 0;
 | 
			
		||||
            Exception exception = null;
 | 
			
		||||
            Aop.CurdBeforeEventArgs before = null;
 | 
			
		||||
 | 
			
		||||
            if (_identCol == null || _source.Count > 1)
 | 
			
		||||
            {
 | 
			
		||||
                before = new Aop.CurdBeforeEventArgs(_table.Type, Aop.CurdType.Insert, sql, _params);
 | 
			
		||||
                _orm.Aop.CurdBefore?.Invoke(this, before);
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    ret = await _orm.Ado.ExecuteNonQueryAsync(_connection, _transaction, CommandType.Text, sql, _params);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    exception = ex;
 | 
			
		||||
                    throw ex;
 | 
			
		||||
                }
 | 
			
		||||
                finally
 | 
			
		||||
                {
 | 
			
		||||
                    var after = new Aop.CurdAfterEventArgs(before, exception, ret);
 | 
			
		||||
                    _orm.Aop.CurdAfter?.Invoke(this, after);
 | 
			
		||||
                }
 | 
			
		||||
                return 0;
 | 
			
		||||
            }
 | 
			
		||||
            var identColName = _commonUtils.QuoteSqlName(_identCol.Attribute.Name);
 | 
			
		||||
            var identParam = _commonUtils.AppendParamter(null, $"{_identCol.CsName}99", _identCol.Attribute.MapType, 0);
 | 
			
		||||
            identParam.Direction = ParameterDirection.Output;
 | 
			
		||||
            sql = $"{sql} RETURNING {identColName} INTO {identParam.ParameterName}";
 | 
			
		||||
            var dbParms = _params.Concat(new[] { identParam }).ToArray();
 | 
			
		||||
            before = new Aop.CurdBeforeEventArgs(_table.Type, Aop.CurdType.Insert, sql, dbParms);
 | 
			
		||||
            _orm.Aop.CurdBefore?.Invoke(this, before);
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                await _orm.Ado.ExecuteNonQueryAsync(_connection, _transaction, CommandType.Text, sql, dbParms);
 | 
			
		||||
                long.TryParse(string.Concat(identParam.Value), out ret);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                exception = ex;
 | 
			
		||||
                throw ex;
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                var after = new Aop.CurdAfterEventArgs(before, exception, ret);
 | 
			
		||||
                _orm.Aop.CurdAfter?.Invoke(this, after);
 | 
			
		||||
            }
 | 
			
		||||
            return ret;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override List<T1> RawExecuteInserted()
 | 
			
		||||
        {
 | 
			
		||||
            var sql = this.ToSql();
 | 
			
		||||
            if (string.IsNullOrEmpty(sql)) return new List<T1>();
 | 
			
		||||
 | 
			
		||||
            this.RawExecuteAffrows();
 | 
			
		||||
            return _source;
 | 
			
		||||
        }
 | 
			
		||||
        async protected override Task<List<T1>> RawExecuteInsertedAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var sql = this.ToSql();
 | 
			
		||||
            if (string.IsNullOrEmpty(sql)) return new List<T1>();
 | 
			
		||||
 | 
			
		||||
            await this.RawExecuteAffrowsAsync();
 | 
			
		||||
            return _source;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										185
									
								
								Providers/FreeSql.Provider.Odbc/Oracle/Curd/OdbcOracleSelect.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								Providers/FreeSql.Provider.Odbc/Oracle/Curd/OdbcOracleSelect.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,185 @@
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using FreeSql.Internal.Model;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Linq.Expressions;
 | 
			
		||||
using System.Text;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.Oracle
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    class OdbcOracleSelect<T1> : FreeSql.Internal.CommonProvider.Select1Provider<T1> where T1 : class
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        internal static string ToSqlStatic(CommonUtils _commonUtils, CommonExpression _commonExpression, string _select, bool _distinct, string field, StringBuilder _join, StringBuilder _where, string _groupby, string _having, string _orderby, int _skip, int _limit, List<SelectTableInfo> _tables, List<Dictionary<Type, string>> tbUnions, List<LambdaExpression> _whereCascadeExpression, IFreeSql _orm)
 | 
			
		||||
        {
 | 
			
		||||
            if (_orm.CodeFirst.IsAutoSyncStructure)
 | 
			
		||||
                _orm.CodeFirst.SyncStructure(_tables.Select(a => a.Table.Type).ToArray());
 | 
			
		||||
 | 
			
		||||
            if (_whereCascadeExpression.Any())
 | 
			
		||||
                foreach (var tb in _tables.Where(a => a.Type != SelectTableInfoType.Parent))
 | 
			
		||||
                    tb.Cascade = _commonExpression.GetWhereCascadeSql(tb, _whereCascadeExpression);
 | 
			
		||||
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            var tbUnionsGt0 = tbUnions.Count > 1;
 | 
			
		||||
            for (var tbUnionsIdx = 0; tbUnionsIdx < tbUnions.Count; tbUnionsIdx++)
 | 
			
		||||
            {
 | 
			
		||||
                if (tbUnionsIdx > 0) sb.Append(" \r\n\r\nUNION ALL\r\n\r\n");
 | 
			
		||||
                if (tbUnionsGt0) sb.Append("select * from (");
 | 
			
		||||
                var tbUnion = tbUnions[tbUnionsIdx];
 | 
			
		||||
 | 
			
		||||
                var sbnav = new StringBuilder();
 | 
			
		||||
                sb.Append(_select);
 | 
			
		||||
                if (_distinct) sb.Append("DISTINCT ");
 | 
			
		||||
                sb.Append(field);
 | 
			
		||||
                if (string.IsNullOrEmpty(_orderby) && _skip > 0) sb.Append(", ROWNUM AS \"__rownum__\"");
 | 
			
		||||
                sb.Append(" \r\nFROM ");
 | 
			
		||||
                var tbsjoin = _tables.Where(a => a.Type != SelectTableInfoType.From).ToArray();
 | 
			
		||||
                var tbsfrom = _tables.Where(a => a.Type == SelectTableInfoType.From).ToArray();
 | 
			
		||||
                for (var a = 0; a < tbsfrom.Length; a++)
 | 
			
		||||
                {
 | 
			
		||||
                    sb.Append(_commonUtils.QuoteSqlName(tbUnion[tbsfrom[a].Table.Type])).Append(" ").Append(tbsfrom[a].Alias);
 | 
			
		||||
                    if (tbsjoin.Length > 0)
 | 
			
		||||
                    {
 | 
			
		||||
                        //如果存在 join 查询,则处理 from t1, t2 改为 from t1 inner join t2 on 1 = 1
 | 
			
		||||
                        for (var b = 1; b < tbsfrom.Length; b++)
 | 
			
		||||
                        {
 | 
			
		||||
                            sb.Append(" \r\nLEFT JOIN ").Append(_commonUtils.QuoteSqlName(tbUnion[tbsfrom[b].Table.Type])).Append(" ").Append(tbsfrom[b].Alias);
 | 
			
		||||
 | 
			
		||||
                            if (string.IsNullOrEmpty(tbsfrom[b].NavigateCondition) && string.IsNullOrEmpty(tbsfrom[b].On) && string.IsNullOrEmpty(tbsfrom[b].Cascade)) sb.Append(" ON 1 = 1");
 | 
			
		||||
                            else
 | 
			
		||||
                            {
 | 
			
		||||
                                sb.Append(" ON ").Append(tbsfrom[b].NavigateCondition ?? tbsfrom[b].On);
 | 
			
		||||
                                if (string.IsNullOrEmpty(tbsfrom[b].Cascade) == false) sb.Append(" AND (").Append(tbsfrom[b].Cascade).Append(")");
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        if (!string.IsNullOrEmpty(tbsfrom[a].NavigateCondition)) sbnav.Append(" AND (").Append(tbsfrom[a].NavigateCondition).Append(")");
 | 
			
		||||
                        if (!string.IsNullOrEmpty(tbsfrom[a].On)) sbnav.Append(" AND (").Append(tbsfrom[a].On).Append(")");
 | 
			
		||||
                        if (a > 0 && !string.IsNullOrEmpty(tbsfrom[a].Cascade)) sbnav.Append(" AND (").Append(tbsfrom[a].Cascade).Append(")");
 | 
			
		||||
                    }
 | 
			
		||||
                    if (a < tbsfrom.Length - 1) sb.Append(", ");
 | 
			
		||||
                }
 | 
			
		||||
                foreach (var tb in tbsjoin)
 | 
			
		||||
                {
 | 
			
		||||
                    if (tb.Type == SelectTableInfoType.Parent) continue;
 | 
			
		||||
                    switch (tb.Type)
 | 
			
		||||
                    {
 | 
			
		||||
                        case SelectTableInfoType.LeftJoin:
 | 
			
		||||
                            sb.Append(" \r\nLEFT JOIN ");
 | 
			
		||||
                            break;
 | 
			
		||||
                        case SelectTableInfoType.InnerJoin:
 | 
			
		||||
                            sb.Append(" \r\nINNER JOIN ");
 | 
			
		||||
                            break;
 | 
			
		||||
                        case SelectTableInfoType.RightJoin:
 | 
			
		||||
                            sb.Append(" \r\nRIGHT JOIN ");
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                    sb.Append(_commonUtils.QuoteSqlName(tbUnion[tb.Table.Type])).Append(" ").Append(tb.Alias).Append(" ON ").Append(tb.On ?? tb.NavigateCondition);
 | 
			
		||||
                    if (!string.IsNullOrEmpty(tb.Cascade)) sb.Append(" AND (").Append(tb.Cascade).Append(")");
 | 
			
		||||
                    if (!string.IsNullOrEmpty(tb.On) && !string.IsNullOrEmpty(tb.NavigateCondition)) sbnav.Append(" AND (").Append(tb.NavigateCondition).Append(")");
 | 
			
		||||
                }
 | 
			
		||||
                if (_join.Length > 0) sb.Append(_join);
 | 
			
		||||
 | 
			
		||||
                sbnav.Append(_where);
 | 
			
		||||
                if (!string.IsNullOrEmpty(_tables[0].Cascade))
 | 
			
		||||
                    sbnav.Append(" AND (").Append(_tables[0].Cascade).Append(")");
 | 
			
		||||
 | 
			
		||||
                foreach (var tb in _tables)
 | 
			
		||||
                {
 | 
			
		||||
                    if (tb.Type == SelectTableInfoType.Parent) continue;
 | 
			
		||||
                    if (string.IsNullOrEmpty(tb.Table.SelectFilter) == false)
 | 
			
		||||
                        sbnav.Append(" AND (").Append(tb.Table.SelectFilter.Replace("a.", $"{tb.Alias}.")).Append(")");
 | 
			
		||||
                }
 | 
			
		||||
                if (string.IsNullOrEmpty(_orderby) && (_skip > 0 || _limit > 0))
 | 
			
		||||
                    sbnav.Append(" AND ROWNUM < ").Append(_skip + _limit + 1);
 | 
			
		||||
                if (sbnav.Length > 0)
 | 
			
		||||
                    sb.Append(" \r\nWHERE ").Append(sbnav.Remove(0, 5));
 | 
			
		||||
                if (string.IsNullOrEmpty(_groupby) == false)
 | 
			
		||||
                {
 | 
			
		||||
                    sb.Append(_groupby);
 | 
			
		||||
                    if (string.IsNullOrEmpty(_having) == false)
 | 
			
		||||
                        sb.Append(" \r\nHAVING ").Append(_having.Substring(5));
 | 
			
		||||
                }
 | 
			
		||||
                sb.Append(_orderby);
 | 
			
		||||
 | 
			
		||||
                if (string.IsNullOrEmpty(_orderby))
 | 
			
		||||
                {
 | 
			
		||||
                    if (_skip > 0)
 | 
			
		||||
                        sb.Insert(0, "SELECT t.* FROM (").Append(") t WHERE t.\"__rownum__\" > ").Append(_skip);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    if (_skip > 0 && _limit > 0) sb.Insert(0, "SELECT t.* FROM (SELECT rt.*, ROWNUM AS \"__rownum__\" FROM (").Append(") rt WHERE ROWNUM < ").Append(_skip + _limit + 1).Append(") t WHERE t.\"__rownum__\" > ").Append(_skip);
 | 
			
		||||
                    else if (_skip > 0) sb.Insert(0, "SELECT t.* FROM (").Append(") t WHERE ROWNUM > ").Append(_skip);
 | 
			
		||||
                    else if (_limit > 0) sb.Insert(0, "SELECT t.* FROM (").Append(") t WHERE ROWNUM < ").Append(_limit + 1);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                sbnav.Clear();
 | 
			
		||||
                if (tbUnionsGt0) sb.Append(") ftb");
 | 
			
		||||
            }
 | 
			
		||||
            return sb.ToString();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public OdbcOracleSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override ISelect<T1, T2> From<T2>(Expression<Func<ISelectFromExpression<T1>, T2, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new OracleSelect<T1, T2>(_orm, _commonUtils, _commonExpression, null); OdbcOracleSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override ISelect<T1, T2, T3> From<T2, T3>(Expression<Func<ISelectFromExpression<T1>, T2, T3, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new OracleSelect<T1, T2, T3>(_orm, _commonUtils, _commonExpression, null); OdbcOracleSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override ISelect<T1, T2, T3, T4> From<T2, T3, T4>(Expression<Func<ISelectFromExpression<T1>, T2, T3, T4, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new OracleSelect<T1, T2, T3, T4>(_orm, _commonUtils, _commonExpression, null); OdbcOracleSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override ISelect<T1, T2, T3, T4, T5> From<T2, T3, T4, T5>(Expression<Func<ISelectFromExpression<T1>, T2, T3, T4, T5, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new OracleSelect<T1, T2, T3, T4, T5>(_orm, _commonUtils, _commonExpression, null); OdbcOracleSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override ISelect<T1, T2, T3, T4, T5, T6> From<T2, T3, T4, T5, T6>(Expression<Func<ISelectFromExpression<T1>, T2, T3, T4, T5, T6, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new OracleSelect<T1, T2, T3, T4, T5, T6>(_orm, _commonUtils, _commonExpression, null); OdbcOracleSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override ISelect<T1, T2, T3, T4, T5, T6, T7> From<T2, T3, T4, T5, T6, T7>(Expression<Func<ISelectFromExpression<T1>, T2, T3, T4, T5, T6, T7, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new OracleSelect<T1, T2, T3, T4, T5, T6, T7>(_orm, _commonUtils, _commonExpression, null); OdbcOracleSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override ISelect<T1, T2, T3, T4, T5, T6, T7, T8> From<T2, T3, T4, T5, T6, T7, T8>(Expression<Func<ISelectFromExpression<T1>, T2, T3, T4, T5, T6, T7, T8, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new OracleSelect<T1, T2, T3, T4, T5, T6, T7, T8>(_orm, _commonUtils, _commonExpression, null); OdbcOracleSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override ISelect<T1, T2, T3, T4, T5, T6, T7, T8, T9> From<T2, T3, T4, T5, T6, T7, T8, T9>(Expression<Func<ISelectFromExpression<T1>, T2, T3, T4, T5, T6, T7, T8, T9, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new OracleSelect<T1, T2, T3, T4, T5, T6, T7, T8, T9>(_orm, _commonUtils, _commonExpression, null); OdbcOracleSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override ISelect<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> From<T2, T3, T4, T5, T6, T7, T8, T9, T10>(Expression<Func<ISelectFromExpression<T1>, T2, T3, T4, T5, T6, T7, T8, T9, T10, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new OracleSelect<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(_orm, _commonUtils, _commonExpression, null); OdbcOracleSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override string ToSql(string field = null) => ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class OracleSelect<T1, T2> : FreeSql.Internal.CommonProvider.Select2Provider<T1, T2> where T1 : class where T2 : class
 | 
			
		||||
    {
 | 
			
		||||
        public OracleSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcOracleSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class OracleSelect<T1, T2, T3> : FreeSql.Internal.CommonProvider.Select3Provider<T1, T2, T3> where T1 : class where T2 : class where T3 : class
 | 
			
		||||
    {
 | 
			
		||||
        public OracleSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcOracleSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class OracleSelect<T1, T2, T3, T4> : FreeSql.Internal.CommonProvider.Select4Provider<T1, T2, T3, T4> where T1 : class where T2 : class where T3 : class where T4 : class
 | 
			
		||||
    {
 | 
			
		||||
        public OracleSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcOracleSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class OracleSelect<T1, T2, T3, T4, T5> : FreeSql.Internal.CommonProvider.Select5Provider<T1, T2, T3, T4, T5> where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class
 | 
			
		||||
    {
 | 
			
		||||
        public OracleSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcOracleSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class OracleSelect<T1, T2, T3, T4, T5, T6> : FreeSql.Internal.CommonProvider.Select6Provider<T1, T2, T3, T4, T5, T6> where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class
 | 
			
		||||
    {
 | 
			
		||||
        public OracleSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcOracleSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class OracleSelect<T1, T2, T3, T4, T5, T6, T7> : FreeSql.Internal.CommonProvider.Select7Provider<T1, T2, T3, T4, T5, T6, T7> where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class
 | 
			
		||||
    {
 | 
			
		||||
        public OracleSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcOracleSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class OracleSelect<T1, T2, T3, T4, T5, T6, T7, T8> : FreeSql.Internal.CommonProvider.Select8Provider<T1, T2, T3, T4, T5, T6, T7, T8> where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class
 | 
			
		||||
    {
 | 
			
		||||
        public OracleSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcOracleSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class OracleSelect<T1, T2, T3, T4, T5, T6, T7, T8, T9> : FreeSql.Internal.CommonProvider.Select9Provider<T1, T2, T3, T4, T5, T6, T7, T8, T9> where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class where T9 : class
 | 
			
		||||
    {
 | 
			
		||||
        public OracleSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcOracleSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class OracleSelect<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> : FreeSql.Internal.CommonProvider.Select10Provider<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class where T9 : class where T10 : class
 | 
			
		||||
    {
 | 
			
		||||
        public OracleSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcOracleSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,72 @@
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using FreeSql.Internal.Model;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.Oracle
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    class OdbcOracleUpdate<T1> : Internal.CommonProvider.UpdateProvider<T1> where T1 : class
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        public OdbcOracleUpdate(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere)
 | 
			
		||||
            : base(orm, commonUtils, commonExpression, dywhere)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override int ExecuteAffrows() => base.SplitExecuteAffrows(200, 999);
 | 
			
		||||
        public override Task<int> ExecuteAffrowsAsync() => base.SplitExecuteAffrowsAsync(200, 999);
 | 
			
		||||
        public override List<T1> ExecuteUpdated() => base.SplitExecuteUpdated(200, 999);
 | 
			
		||||
        public override Task<List<T1>> ExecuteUpdatedAsync() => base.SplitExecuteUpdatedAsync(200, 999);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        protected override List<T1> RawExecuteUpdated()
 | 
			
		||||
        {
 | 
			
		||||
            throw new NotImplementedException();
 | 
			
		||||
        }
 | 
			
		||||
        protected override Task<List<T1>> RawExecuteUpdatedAsync()
 | 
			
		||||
        {
 | 
			
		||||
            throw new NotImplementedException();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override void ToSqlCase(StringBuilder caseWhen, ColumnInfo[] primarys)
 | 
			
		||||
        {
 | 
			
		||||
            if (_table.Primarys.Length == 1)
 | 
			
		||||
            {
 | 
			
		||||
                caseWhen.Append(_commonUtils.QuoteReadColumn(_table.Primarys.First().Attribute.MapType, _commonUtils.QuoteSqlName(_table.Primarys.First().Attribute.Name)));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            caseWhen.Append("(");
 | 
			
		||||
            var pkidx = 0;
 | 
			
		||||
            foreach (var pk in _table.Primarys)
 | 
			
		||||
            {
 | 
			
		||||
                if (pkidx > 0) caseWhen.Append(" || ");
 | 
			
		||||
                caseWhen.Append(_commonUtils.QuoteReadColumn(pk.Attribute.MapType, _commonUtils.QuoteSqlName(pk.Attribute.Name)));
 | 
			
		||||
                ++pkidx;
 | 
			
		||||
            }
 | 
			
		||||
            caseWhen.Append(")");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override void ToSqlWhen(StringBuilder sb, ColumnInfo[] primarys, object d)
 | 
			
		||||
        {
 | 
			
		||||
            if (_table.Primarys.Length == 1)
 | 
			
		||||
            {
 | 
			
		||||
                sb.Append(_commonUtils.FormatSql("{0}", _table.Primarys.First().GetMapValue(d)));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            sb.Append("(");
 | 
			
		||||
            var pkidx = 0;
 | 
			
		||||
            foreach (var pk in _table.Primarys)
 | 
			
		||||
            {
 | 
			
		||||
                if (pkidx > 0) sb.Append(" || ");
 | 
			
		||||
                sb.Append(_commonUtils.FormatSql("{0}", pk.GetMapValue(d)));
 | 
			
		||||
                ++pkidx;
 | 
			
		||||
            }
 | 
			
		||||
            sb.Append(")");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,70 @@
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using SafeObjectPool;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections;
 | 
			
		||||
using System.Data.Common;
 | 
			
		||||
using System.Data.Odbc;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.Oracle
 | 
			
		||||
{
 | 
			
		||||
    class OdbcOracleAdo : FreeSql.Internal.CommonProvider.AdoProvider
 | 
			
		||||
    {
 | 
			
		||||
        public OdbcOracleAdo() : base(DataType.Oracle) { }
 | 
			
		||||
        public OdbcOracleAdo(CommonUtils util, string masterConnectionString, string[] slaveConnectionStrings) : base(DataType.Oracle)
 | 
			
		||||
        {
 | 
			
		||||
            base._util = util;
 | 
			
		||||
            if (!string.IsNullOrEmpty(masterConnectionString))
 | 
			
		||||
                MasterPool = new OdbcOracleConnectionPool("主库", masterConnectionString, null, null);
 | 
			
		||||
            if (slaveConnectionStrings != null)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var slaveConnectionString in slaveConnectionStrings)
 | 
			
		||||
                {
 | 
			
		||||
                    var slavePool = new OdbcOracleConnectionPool($"从库{SlavePools.Count + 1}", slaveConnectionString, () => Interlocked.Decrement(ref slaveUnavailables), () => Interlocked.Increment(ref slaveUnavailables));
 | 
			
		||||
                    SlavePools.Add(slavePool);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        static DateTime dt1970 = new DateTime(1970, 1, 1);
 | 
			
		||||
        public override object AddslashesProcessParam(object param, Type mapType)
 | 
			
		||||
        {
 | 
			
		||||
            if (param == null) return "NULL";
 | 
			
		||||
            if (mapType != null && mapType != param.GetType())
 | 
			
		||||
                param = Utils.GetDataReaderValue(mapType, param);
 | 
			
		||||
            if (param is bool || param is bool?)
 | 
			
		||||
                return (bool)param ? 1 : 0;
 | 
			
		||||
            else if (param is string || param is char)
 | 
			
		||||
                return string.Concat("'", param.ToString().Replace("'", "''"), "'");
 | 
			
		||||
            else if (param is Enum)
 | 
			
		||||
                return ((Enum)param).ToInt64();
 | 
			
		||||
            else if (decimal.TryParse(string.Concat(param), out var trydec))
 | 
			
		||||
                return param;
 | 
			
		||||
            else if (param is DateTime || param is DateTime?)
 | 
			
		||||
                return string.Concat("to_timestamp('", ((DateTime)param).ToString("yyyy-MM-dd HH:mm:ss.ffffff"), "','YYYY-MM-DD HH24:MI:SS.FF6')");
 | 
			
		||||
            else if (param is TimeSpan || param is TimeSpan?)
 | 
			
		||||
                return $"numtodsinterval({((TimeSpan)param).Ticks * 1.0 / 10000000},'second')";
 | 
			
		||||
            else if (param is IEnumerable)
 | 
			
		||||
            {
 | 
			
		||||
                var sb = new StringBuilder();
 | 
			
		||||
                var ie = param as IEnumerable;
 | 
			
		||||
                foreach (var z in ie) sb.Append(",").Append(AddslashesProcessParam(z, mapType));
 | 
			
		||||
                return sb.Length == 0 ? "(NULL)" : sb.Remove(0, 1).Insert(0, "(").Append(")").ToString();
 | 
			
		||||
            }
 | 
			
		||||
            return string.Concat("'", param.ToString().Replace("'", "''"), "'");
 | 
			
		||||
            //if (param is string) return string.Concat('N', nparms[a]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override DbCommand CreateCommand()
 | 
			
		||||
        {
 | 
			
		||||
            return new OdbcCommand();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override void ReturnConnection(ObjectPool<DbConnection> pool, Object<DbConnection> conn, Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            (pool as OdbcOracleConnectionPool).Return(conn, ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override DbParameter[] GetDbParamtersByObject(string sql, object obj) => _util.GetDbParamtersByObject(sql, obj);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,246 @@
 | 
			
		||||
using SafeObjectPool;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data;
 | 
			
		||||
using System.Data.Common;
 | 
			
		||||
using System.Data.Odbc;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.Oracle
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    class OdbcOracleConnectionPool : ObjectPool<DbConnection>
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        internal Action availableHandler;
 | 
			
		||||
        internal Action unavailableHandler;
 | 
			
		||||
        internal string UserId { get; set; }
 | 
			
		||||
 | 
			
		||||
        public OdbcOracleConnectionPool(string name, string connectionString, Action availableHandler, Action unavailableHandler) : base(null)
 | 
			
		||||
        {
 | 
			
		||||
            var userIdMatch = Regex.Match(connectionString, @"(User\s+Id|Uid)\s*=\s*([^;]+)", RegexOptions.IgnoreCase);
 | 
			
		||||
            if (userIdMatch.Success == false) throw new Exception(@"从 ConnectionString 中无法匹配 (User\s+Id|Uid)\s*=\s*([^;]+)");
 | 
			
		||||
            this.UserId = userIdMatch.Groups[2].Value.Trim().ToUpper();
 | 
			
		||||
 | 
			
		||||
            var policy = new OdbcOracleConnectionPoolPolicy
 | 
			
		||||
            {
 | 
			
		||||
                _pool = this,
 | 
			
		||||
                Name = name
 | 
			
		||||
            };
 | 
			
		||||
            this.Policy = policy;
 | 
			
		||||
            policy.ConnectionString = connectionString;
 | 
			
		||||
 | 
			
		||||
            this.availableHandler = availableHandler;
 | 
			
		||||
            this.unavailableHandler = unavailableHandler;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Return(Object<DbConnection> obj, Exception exception, bool isRecreate = false)
 | 
			
		||||
        {
 | 
			
		||||
            if (exception != null && exception is OdbcException)
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
                if (exception is System.IO.IOException)
 | 
			
		||||
                {
 | 
			
		||||
 | 
			
		||||
                    base.SetUnavailable(exception);
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
                else if (obj.Value.Ping() == false)
 | 
			
		||||
                {
 | 
			
		||||
 | 
			
		||||
                    base.SetUnavailable(exception);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            base.Return(obj, isRecreate);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    class OdbcOracleConnectionPoolPolicy : IPolicy<DbConnection>
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        internal OdbcOracleConnectionPool _pool;
 | 
			
		||||
        public string Name { get; set; } = "Oracle OdbcConnection 对象池";
 | 
			
		||||
        public int PoolSize { get; set; } = 100;
 | 
			
		||||
        public TimeSpan SyncGetTimeout { get; set; } = TimeSpan.FromSeconds(10);
 | 
			
		||||
        public TimeSpan IdleTimeout { get; set; } = TimeSpan.Zero;
 | 
			
		||||
        public int AsyncGetCapacity { get; set; } = 10000;
 | 
			
		||||
        public bool IsThrowGetTimeoutException { get; set; } = true;
 | 
			
		||||
        public int CheckAvailableInterval { get; set; } = 5;
 | 
			
		||||
 | 
			
		||||
        static ConcurrentDictionary<string, int> dicConnStrIncr = new ConcurrentDictionary<string, int>(StringComparer.CurrentCultureIgnoreCase);
 | 
			
		||||
        private string _connectionString;
 | 
			
		||||
        public string ConnectionString
 | 
			
		||||
        {
 | 
			
		||||
            get => _connectionString;
 | 
			
		||||
            set
 | 
			
		||||
            {
 | 
			
		||||
                _connectionString = value ?? "";
 | 
			
		||||
 | 
			
		||||
                var pattern = @"Max\s*pool\s*size\s*=\s*(\d+)";
 | 
			
		||||
                Match m = Regex.Match(_connectionString, pattern, RegexOptions.IgnoreCase);
 | 
			
		||||
                if (m.Success == false || int.TryParse(m.Groups[1].Value, out var poolsize) == false || poolsize <= 0) poolsize = 100;
 | 
			
		||||
                var connStrIncr = dicConnStrIncr.AddOrUpdate(_connectionString, 1, (oldkey, oldval) => oldval + 1);
 | 
			
		||||
                PoolSize = poolsize + connStrIncr;
 | 
			
		||||
                _connectionString = m.Success ?
 | 
			
		||||
                    Regex.Replace(_connectionString, pattern, $"Max pool size={PoolSize}", RegexOptions.IgnoreCase) :
 | 
			
		||||
                    $"{_connectionString};Max pool size={PoolSize}";
 | 
			
		||||
 | 
			
		||||
                pattern = @"Connection\s*LifeTime\s*=\s*(\d+)";
 | 
			
		||||
                m = Regex.Match(_connectionString, pattern, RegexOptions.IgnoreCase);
 | 
			
		||||
                if (m.Success)
 | 
			
		||||
                {
 | 
			
		||||
                    IdleTimeout = TimeSpan.FromSeconds(int.Parse(m.Groups[1].Value));
 | 
			
		||||
                    _connectionString = Regex.Replace(_connectionString, pattern, "", RegexOptions.IgnoreCase);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var minPoolSize = 0;
 | 
			
		||||
                pattern = @"Min\s*pool\s*size\s*=\s*(\d+)";
 | 
			
		||||
                m = Regex.Match(_connectionString, pattern, RegexOptions.IgnoreCase);
 | 
			
		||||
                if (m.Success)
 | 
			
		||||
                {
 | 
			
		||||
                    minPoolSize = int.Parse(m.Groups[1].Value);
 | 
			
		||||
                    _connectionString = Regex.Replace(_connectionString, pattern, "", RegexOptions.IgnoreCase);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                FreeSql.Internal.CommonUtils.PrevReheatConnectionPool(_pool, minPoolSize);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool OnCheckAvailable(Object<DbConnection> obj)
 | 
			
		||||
        {
 | 
			
		||||
            if (obj.Value.State == ConnectionState.Closed) obj.Value.Open();
 | 
			
		||||
            return obj.Value.Ping(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public DbConnection OnCreate()
 | 
			
		||||
        {
 | 
			
		||||
            var conn = new OdbcConnection(_connectionString);
 | 
			
		||||
            return conn;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnDestroy(DbConnection obj)
 | 
			
		||||
        {
 | 
			
		||||
            if (obj.State != ConnectionState.Closed) obj.Close();
 | 
			
		||||
            obj.Dispose();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnGet(Object<DbConnection> obj)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            if (_pool.IsAvailable)
 | 
			
		||||
            {
 | 
			
		||||
                if (obj.Value == null)
 | 
			
		||||
                {
 | 
			
		||||
                    if (_pool.SetUnavailable(new Exception("连接字符串错误")) == true)
 | 
			
		||||
                        throw new Exception($"【{this.Name}】连接字符串错误,请检查。");
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (obj.Value.State != ConnectionState.Open || DateTime.Now.Subtract(obj.LastReturnTime).TotalSeconds > 60 && obj.Value.Ping() == false)
 | 
			
		||||
                {
 | 
			
		||||
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        obj.Value.Open();
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (Exception ex)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (_pool.SetUnavailable(ex) == true)
 | 
			
		||||
                            throw new Exception($"【{this.Name}】状态不可用,等待后台检查程序恢复方可使用。{ex.Message}");
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        async public Task OnGetAsync(Object<DbConnection> obj)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            if (_pool.IsAvailable)
 | 
			
		||||
            {
 | 
			
		||||
                if (obj.Value == null)
 | 
			
		||||
                {
 | 
			
		||||
                    if (_pool.SetUnavailable(new Exception("连接字符串错误")) == true)
 | 
			
		||||
                        throw new Exception($"【{this.Name}】连接字符串错误,请检查。");
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (obj.Value.State != ConnectionState.Open || DateTime.Now.Subtract(obj.LastReturnTime).TotalSeconds > 60 && (await obj.Value.PingAsync()) == false)
 | 
			
		||||
                {
 | 
			
		||||
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        await obj.Value.OpenAsync();
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (Exception ex)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (_pool.SetUnavailable(ex) == true)
 | 
			
		||||
                            throw new Exception($"【{this.Name}】状态不可用,等待后台检查程序恢复方可使用。{ex.Message}");
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnGetTimeout()
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnReturn(Object<DbConnection> obj)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnAvailable()
 | 
			
		||||
        {
 | 
			
		||||
            _pool.availableHandler?.Invoke();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnUnavailable()
 | 
			
		||||
        {
 | 
			
		||||
            _pool.unavailableHandler?.Invoke();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static class DbConnectionExtensions
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        static DbCommand PingCommand(DbConnection conn)
 | 
			
		||||
        {
 | 
			
		||||
            var cmd = conn.CreateCommand();
 | 
			
		||||
            cmd.CommandTimeout = 5;
 | 
			
		||||
            cmd.CommandText = "select 1 from dual";
 | 
			
		||||
            return cmd;
 | 
			
		||||
        }
 | 
			
		||||
        public static bool Ping(this DbConnection that, bool isThrow = false)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                PingCommand(that).ExecuteNonQuery();
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                if (that.State != ConnectionState.Closed) try { that.Close(); } catch { }
 | 
			
		||||
                if (isThrow) throw;
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        async public static Task<bool> PingAsync(this DbConnection that, bool isThrow = false)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                await PingCommand(that).ExecuteNonQueryAsync();
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                if (that.State != ConnectionState.Closed) try { that.Close(); } catch { }
 | 
			
		||||
                if (isThrow) throw;
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										412
									
								
								Providers/FreeSql.Provider.Odbc/Oracle/OdbcOracleCodeFirst.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										412
									
								
								Providers/FreeSql.Provider.Odbc/Oracle/OdbcOracleCodeFirst.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,412 @@
 | 
			
		||||
using FreeSql.DataAnnotations;
 | 
			
		||||
using FreeSql.DatabaseModel;
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using FreeSql.Internal.Model;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data;
 | 
			
		||||
using System.Data.Odbc;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.Oracle
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    class OdbcOracleCodeFirst : Internal.CommonProvider.CodeFirstProvider
 | 
			
		||||
    {
 | 
			
		||||
        public override bool IsNoneCommandParameter { get => true; set => base.IsNoneCommandParameter = true; }
 | 
			
		||||
        public OdbcOracleCodeFirst(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) : base(orm, commonUtils, commonExpression) { }
 | 
			
		||||
 | 
			
		||||
        static object _dicCsToDbLock = new object();
 | 
			
		||||
        static Dictionary<string, (OdbcType type, string dbtype, string dbtypeFull, bool? isUnsigned, bool? isnullable, object defaultValue)> _dicCsToDb = new Dictionary<string, (OdbcType type, string dbtype, string dbtypeFull, bool? isUnsigned, bool? isnullable, object defaultValue)>() {
 | 
			
		||||
                { typeof(bool).FullName,  (OdbcType.Bit, "number","number(1) NOT NULL", null, false, false) },{ typeof(bool?).FullName,  (OdbcType.Bit, "number","number(1) NULL", null, true, null) },
 | 
			
		||||
 | 
			
		||||
                { typeof(sbyte).FullName,  (OdbcType.SmallInt, "number", "number(4) NOT NULL", false, false, 0) },{ typeof(sbyte?).FullName,  (OdbcType.SmallInt, "number", "number(4) NULL", false, true, null) },
 | 
			
		||||
                { typeof(short).FullName,  (OdbcType.SmallInt, "number","number(6) NOT NULL", false, false, 0) },{ typeof(short?).FullName,  (OdbcType.SmallInt, "number", "number(6) NULL", false, true, null) },
 | 
			
		||||
                { typeof(int).FullName,  (OdbcType.Int, "number", "number(11) NOT NULL", false, false, 0) },{ typeof(int?).FullName,  (OdbcType.Int, "number", "number(11) NULL", false, true, null) },
 | 
			
		||||
                { typeof(long).FullName,  (OdbcType.BigInt, "number","number(21) NOT NULL", false, false, 0) },{ typeof(long?).FullName,  (OdbcType.BigInt, "number","number(21) NULL", false, true, null) },
 | 
			
		||||
 | 
			
		||||
                { typeof(byte).FullName,  (OdbcType.TinyInt, "number","number(3) NOT NULL", true, false, 0) },{ typeof(byte?).FullName,  (OdbcType.TinyInt, "number","number(3) NULL", true, true, null) },
 | 
			
		||||
                { typeof(ushort).FullName,  (OdbcType.Int, "number","number(5) NOT NULL", true, false, 0) },{ typeof(ushort?).FullName,  (OdbcType.Int, "number", "number(5) NULL", true, true, null) },
 | 
			
		||||
                { typeof(uint).FullName,  (OdbcType.BigInt, "number", "number(10) NOT NULL", true, false, 0) },{ typeof(uint?).FullName,  (OdbcType.BigInt, "number", "number(10) NULL", true, true, null) },
 | 
			
		||||
                { typeof(ulong).FullName,  (OdbcType.Decimal, "number", "number(20) NOT NULL", true, false, 0) },{ typeof(ulong?).FullName,  (OdbcType.Decimal, "number", "number(20) NULL", true, true, null) },
 | 
			
		||||
 | 
			
		||||
                { typeof(double).FullName,  (OdbcType.Double, "float", "float(126) NOT NULL", false, false, 0) },{ typeof(double?).FullName,  (OdbcType.Double, "float", "float(126) NULL", false, true, null) },
 | 
			
		||||
                { typeof(float).FullName,  (OdbcType.Real, "float","float(63) NOT NULL", false, false, 0) },{ typeof(float?).FullName,  (OdbcType.Real, "float","float(63) NULL", false, true, null) },
 | 
			
		||||
                { typeof(decimal).FullName,  (OdbcType.Decimal, "number", "number(10,2) NOT NULL", false, false, 0) },{ typeof(decimal?).FullName,  (OdbcType.Decimal, "number", "number(10,2) NULL", false, true, null) },
 | 
			
		||||
 | 
			
		||||
                //{ typeof(TimeSpan).FullName,  (OdbcType.Time, "interval day to second","interval day(2) to second(6) NOT NULL", false, false, 0) },{ typeof(TimeSpan?).FullName,  (OdbcType.Time, "interval day to second", "interval day(2) to second(6) NULL",false, true, null) },
 | 
			
		||||
                { typeof(DateTime).FullName,  (OdbcType.DateTime, "timestamp", "timestamp(6) NOT NULL", false, false, new DateTime(1970,1,1)) },{ typeof(DateTime?).FullName,  (OdbcType.DateTime, "timestamp", "timestamp(6) NULL", false, true, null) },
 | 
			
		||||
                { typeof(DateTimeOffset).FullName,  (OdbcType.DateTime, "timestamp with local time zone", "timestamp(6) with local time zone NOT NULL", false, false, new DateTime(1970,1,1)) },{ typeof(DateTimeOffset?).FullName,  (OdbcType.DateTime, "timestamp with local time zone", "timestamp(6) with local time zone NULL", false, true, null) },
 | 
			
		||||
 | 
			
		||||
                { typeof(byte[]).FullName,  (OdbcType.VarBinary, "blob", "blob NULL", false, null, new byte[0]) },
 | 
			
		||||
                { typeof(string).FullName,  (OdbcType.NVarChar, "nvarchar2", "nvarchar2(255) NULL", false, null, "") },
 | 
			
		||||
 | 
			
		||||
                { typeof(Guid).FullName,  (OdbcType.Char, "char", "char(36 CHAR) NOT NULL", false, false, Guid.Empty) },{ typeof(Guid?).FullName,  (OdbcType.Char, "char", "char(36 CHAR) NULL", false, true, null) },
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
        public override (int type, string dbtype, string dbtypeFull, bool? isnullable, object defaultValue)? GetDbInfo(Type type)
 | 
			
		||||
        {
 | 
			
		||||
            if (_dicCsToDb.TryGetValue(type.FullName, out var trydc)) return new (int, string, string, bool?, object)?(((int)trydc.type, trydc.dbtype, trydc.dbtypeFull, trydc.isnullable, trydc.defaultValue));
 | 
			
		||||
            if (type.IsArray) return null;
 | 
			
		||||
            var enumType = type.IsEnum ? type : null;
 | 
			
		||||
            if (enumType == null && type.IsNullableType() && type.GenericTypeArguments.Length == 1 && type.GenericTypeArguments.First().IsEnum) enumType = type.GenericTypeArguments.First();
 | 
			
		||||
            if (enumType != null)
 | 
			
		||||
            {
 | 
			
		||||
                var newItem = enumType.GetCustomAttributes(typeof(FlagsAttribute), false).Any() ?
 | 
			
		||||
                    (OdbcType.Int, "number", $"number(16){(type.IsEnum ? " NOT NULL" : "")}", false, type.IsEnum ? false : true, Enum.GetValues(enumType).GetValue(0)) :
 | 
			
		||||
                    (OdbcType.BigInt, "number", $"number(32){(type.IsEnum ? " NOT NULL" : "")}", false, type.IsEnum ? false : true, Enum.GetValues(enumType).GetValue(0));
 | 
			
		||||
                if (_dicCsToDb.ContainsKey(type.FullName) == false)
 | 
			
		||||
                {
 | 
			
		||||
                    lock (_dicCsToDbLock)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (_dicCsToDb.ContainsKey(type.FullName) == false)
 | 
			
		||||
                            _dicCsToDb.Add(type.FullName, newItem);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                return ((int)newItem.Item1, newItem.Item2, newItem.Item3, newItem.Item5, newItem.Item6);
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override string GetComparisonDDLStatements(params Type[] entityTypes)
 | 
			
		||||
        {
 | 
			
		||||
            var userId = (_orm.Ado.MasterPool as OdbcOracleConnectionPool).UserId;
 | 
			
		||||
            var seqcols = new List<(ColumnInfo, string[], bool)>(); //序列:列,表,自增
 | 
			
		||||
            var seqnameDel = new List<string>(); //要删除的序列+触发器
 | 
			
		||||
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            var sbDeclare = new StringBuilder();
 | 
			
		||||
            foreach (var entityType in entityTypes)
 | 
			
		||||
            {
 | 
			
		||||
                if (sb.Length > 0) sb.Append("\r\n");
 | 
			
		||||
                var tb = _commonUtils.GetTableByEntity(entityType);
 | 
			
		||||
                if (tb == null) throw new Exception($"类型 {entityType.FullName} 不可迁移");
 | 
			
		||||
                if (tb.Columns.Any() == false) throw new Exception($"类型 {entityType.FullName} 不可迁移,可迁移属性0个");
 | 
			
		||||
                var tbname = tb.DbName.Split(new[] { '.' }, 2);
 | 
			
		||||
                if (tbname?.Length == 1) tbname = new[] { userId, tbname[0] };
 | 
			
		||||
 | 
			
		||||
                var tboldname = tb.DbOldName?.Split(new[] { '.' }, 2); //旧表名
 | 
			
		||||
                if (tboldname?.Length == 1) tboldname = new[] { userId, tboldname[0] };
 | 
			
		||||
                var primaryKeyName = entityType.GetCustomAttribute<OraclePrimaryKeyNameAttribute>()?.Name;
 | 
			
		||||
 | 
			
		||||
                if (string.Compare(tbname[0], userId) != 0 && _orm.Ado.ExecuteScalar(CommandType.Text, _commonUtils.FormatSql(" select 1 from sys.dba_users where username={0}", tbname[0])) == null) //创建数据库
 | 
			
		||||
                    throw new NotImplementedException($"Oracle CodeFirst 不支持代码创建 tablespace 与 schemas {tbname[0]}");
 | 
			
		||||
 | 
			
		||||
                var sbalter = new StringBuilder();
 | 
			
		||||
                var istmpatler = false; //创建临时表,导入数据,删除旧表,修改
 | 
			
		||||
                if (_orm.Ado.ExecuteScalar(CommandType.Text, _commonUtils.FormatSql(" select 1 from all_tab_comments where owner={0} and table_name={1}", tbname)) == null)
 | 
			
		||||
                { //表不存在
 | 
			
		||||
                    if (tboldname != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (_orm.Ado.ExecuteScalar(CommandType.Text, _commonUtils.FormatSql(" select 1 from all_tab_comments where owner={0} and table_name={1}", tboldname)) == null)
 | 
			
		||||
                            //模式或表不存在
 | 
			
		||||
                            tboldname = null;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (tboldname == null)
 | 
			
		||||
                    {
 | 
			
		||||
                        //创建表
 | 
			
		||||
                        sb.Append("execute immediate 'CREATE TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" ( ");
 | 
			
		||||
                        foreach (var tbcol in tb.ColumnsByPosition)
 | 
			
		||||
                        {
 | 
			
		||||
                            sb.Append(" \r\n  ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ").Append(tbcol.Attribute.DbType).Append(",");
 | 
			
		||||
                            if (tbcol.Attribute.IsIdentity == true) seqcols.Add((tbcol, tbname, true));
 | 
			
		||||
                        }
 | 
			
		||||
                        if (tb.Primarys.Any())
 | 
			
		||||
                        {
 | 
			
		||||
                            var pkname = primaryKeyName ?? $"{tbname[0]}_{tbname[1]}_pk1";
 | 
			
		||||
                            sb.Append(" \r\n  CONSTRAINT ").Append(pkname).Append(" PRIMARY KEY (");
 | 
			
		||||
                            foreach (var tbcol in tb.Primarys) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", ");
 | 
			
		||||
                            sb.Remove(sb.Length - 2, 2).Append("),");
 | 
			
		||||
                        }
 | 
			
		||||
                        foreach (var uk in tb.Uniques)
 | 
			
		||||
                        {
 | 
			
		||||
                            sb.Append(" \r\n  CONSTRAINT ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append(" UNIQUE (");
 | 
			
		||||
                            foreach (var tbcol in uk.Value) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", ");
 | 
			
		||||
                            sb.Remove(sb.Length - 2, 2).Append("),");
 | 
			
		||||
                        }
 | 
			
		||||
                        sb.Remove(sb.Length - 1, 1);
 | 
			
		||||
                        sb.Append("\r\n) \r\nLOGGING \r\nNOCOMPRESS \r\nNOCACHE\r\n';\r\n");
 | 
			
		||||
                        //备注
 | 
			
		||||
                        foreach (var tbcol in tb.ColumnsByPosition)
 | 
			
		||||
                        {
 | 
			
		||||
                            if (string.IsNullOrEmpty(tbcol.Comment) == false)
 | 
			
		||||
                                sb.Append("execute immediate 'COMMENT ON COLUMN ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}.{tbcol.Attribute.Name}")).Append(" IS ").Append(_commonUtils.FormatSql("{0}", tbcol.Comment).Replace("'", "''")).Append("';\r\n");
 | 
			
		||||
                        }
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                    //如果新表,旧表在一个模式下,直接修改表名
 | 
			
		||||
                    if (string.Compare(tbname[0], tboldname[0], true) == 0)
 | 
			
		||||
                        sbalter.Append("execute immediate 'ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tboldname[0]}.{tboldname[1]}")).Append(" RENAME TO ").Append(_commonUtils.QuoteSqlName($"{tbname[1]}")).Append("';\r\n");
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        //如果新表,旧表不在一起,创建新表,导入数据,删除旧表
 | 
			
		||||
                        istmpatler = true;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                    tboldname = null; //如果新表已经存在,不走改表名逻辑
 | 
			
		||||
 | 
			
		||||
                //对比字段,只可以修改类型、增加字段、有限的修改字段名;保证安全不删除字段
 | 
			
		||||
                var sql = _commonUtils.FormatSql($@"
 | 
			
		||||
select 
 | 
			
		||||
a.column_name,
 | 
			
		||||
a.data_type,
 | 
			
		||||
a.data_length,
 | 
			
		||||
a.data_precision,
 | 
			
		||||
a.data_scale,
 | 
			
		||||
a.char_used,
 | 
			
		||||
case when a.nullable = 'Y' then 1 else 0 end,
 | 
			
		||||
nvl((select 1 from user_sequences where sequence_name='{Utils.GetCsName((tboldname ?? tbname).Last())}_seq_'||a.column_name), 0),
 | 
			
		||||
nvl((select 1 from user_triggers where trigger_name='{Utils.GetCsName((tboldname ?? tbname).Last())}_seq_'||a.column_name||'TI'), 0),
 | 
			
		||||
b.comments
 | 
			
		||||
from all_tab_columns a
 | 
			
		||||
left join all_col_comments b on b.owner = a.owner and b.table_name = a.table_name and b.column_name = a.column_name
 | 
			
		||||
where a.owner={{0}} and a.table_name={{1}}", tboldname ?? tbname);
 | 
			
		||||
                var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql);
 | 
			
		||||
                var tbstruct = ds.ToDictionary(a => string.Concat(a[0]), a =>
 | 
			
		||||
                {
 | 
			
		||||
                    var sqlType = GetOracleSqlTypeFullName(a);
 | 
			
		||||
                    return new
 | 
			
		||||
                    {
 | 
			
		||||
                        column = string.Concat(a[0]),
 | 
			
		||||
                        sqlType,
 | 
			
		||||
                        is_nullable = string.Concat(a[6]) == "1",
 | 
			
		||||
                        is_identity = string.Concat(a[7]) == "1" && string.Concat(a[8]) == "1",
 | 
			
		||||
                        comment = string.Concat(a[9])
 | 
			
		||||
                    };
 | 
			
		||||
                }, StringComparer.CurrentCultureIgnoreCase);
 | 
			
		||||
 | 
			
		||||
                if (istmpatler == false)
 | 
			
		||||
                {
 | 
			
		||||
                    foreach (var tbcol in tb.ColumnsByPosition)
 | 
			
		||||
                    {
 | 
			
		||||
                        var dbtypeNoneNotNull = Regex.Replace(tbcol.Attribute.DbType, @"NOT\s+NULL", "NULL");
 | 
			
		||||
                        if (tbstruct.TryGetValue(tbcol.Attribute.Name, out var tbstructcol) ||
 | 
			
		||||
                            string.IsNullOrEmpty(tbcol.Attribute.OldName) == false && tbstruct.TryGetValue(tbcol.Attribute.OldName, out tbstructcol))
 | 
			
		||||
                        {
 | 
			
		||||
                            var isCommentChanged = tbstructcol.comment != (tbcol.Comment ?? "");
 | 
			
		||||
                            if (tbcol.Attribute.DbType.StartsWith(tbstructcol.sqlType, StringComparison.CurrentCultureIgnoreCase) == false)
 | 
			
		||||
                                istmpatler = true;
 | 
			
		||||
                            //sbalter.Append("execute immediate 'ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" MODIFY (").Append(_commonUtils.QuoteSqlName(tbstructcol.column)).Append(" ").Append(dbtypeNoneNotNull).Append(")';\r\n");
 | 
			
		||||
                            if (tbcol.Attribute.IsNullable != tbstructcol.is_nullable)
 | 
			
		||||
                            {
 | 
			
		||||
                                if (tbcol.Attribute.IsNullable == false)
 | 
			
		||||
                                    sbalter.Append("execute immediate 'UPDATE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" SET ").Append(_commonUtils.QuoteSqlName(tbstructcol.column)).Append(" = ").Append(_commonUtils.FormatSql("{0}", tbcol.Attribute.DbDefautValue).Replace("'", "''")).Append(" WHERE ").Append(_commonUtils.QuoteSqlName(tbstructcol.column)).Append(" IS NULL';\r\n");
 | 
			
		||||
                                sbalter.Append("execute immediate 'ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" MODIFY ").Append(_commonUtils.QuoteSqlName(tbstructcol.column)).Append(" ").Append(tbcol.Attribute.IsNullable == true ? "" : "NOT").Append(" NULL';\r\n");
 | 
			
		||||
                            }
 | 
			
		||||
                            if (string.Compare(tbstructcol.column, tbcol.Attribute.OldName, true) == 0)
 | 
			
		||||
                            {
 | 
			
		||||
                                if (tbstructcol.is_identity)
 | 
			
		||||
                                    seqnameDel.Add(Utils.GetCsName($"{tbname[1]}_seq_{tbstructcol.column}"));
 | 
			
		||||
                                //修改列名
 | 
			
		||||
                                sbalter.Append("execute immediate 'ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" RENAME COLUMN ").Append(_commonUtils.QuoteSqlName(tbstructcol.column)).Append(" TO ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append("';\r\n");
 | 
			
		||||
                                if (tbcol.Attribute.IsIdentity)
 | 
			
		||||
                                    seqcols.Add((tbcol, tbname, tbcol.Attribute.IsIdentity == true));
 | 
			
		||||
                            }
 | 
			
		||||
                            else if (tbcol.Attribute.IsIdentity != tbstructcol.is_identity)
 | 
			
		||||
                                seqcols.Add((tbcol, tbname, tbcol.Attribute.IsIdentity == true));
 | 
			
		||||
                            if (isCommentChanged)
 | 
			
		||||
                                sbalter.Append("execute immediate 'COMMENT ON COLUMN ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}.{tbcol.Attribute.Name}")).Append(" IS ").Append(_commonUtils.FormatSql("{0}", tbcol.Comment ?? "").Replace("'", "''")).Append("';\r\n");
 | 
			
		||||
                            continue;
 | 
			
		||||
                        }
 | 
			
		||||
                        //添加列
 | 
			
		||||
                        sbalter.Append("execute immediate 'ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" ADD (").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ").Append(dbtypeNoneNotNull).Append(")';\r\n");
 | 
			
		||||
                        if (tbcol.Attribute.IsNullable == false)
 | 
			
		||||
                        {
 | 
			
		||||
                            sbalter.Append("execute immediate 'UPDATE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" SET ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" = ").Append(_commonUtils.FormatSql("{0}", tbcol.Attribute.DbDefautValue).Replace("'", "''")).Append("';\r\n");
 | 
			
		||||
                            sbalter.Append("execute immediate 'ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" MODIFY ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" NOT NULL';\r\n");
 | 
			
		||||
                        }
 | 
			
		||||
                        if (tbcol.Attribute.IsIdentity == true) seqcols.Add((tbcol, tbname, tbcol.Attribute.IsIdentity == true));
 | 
			
		||||
                        if (string.IsNullOrEmpty(tbcol.Comment) == false) sbalter.Append("execute immediate 'COMMENT ON COLUMN ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}.{tbcol.Attribute.Name}")).Append(" IS ").Append(_commonUtils.FormatSql("{0}", tbcol.Comment ?? "").Replace("'", "''")).Append("';\r\n");
 | 
			
		||||
                    }
 | 
			
		||||
                    var dsuksql = _commonUtils.FormatSql(@"
 | 
			
		||||
select
 | 
			
		||||
c.column_name,
 | 
			
		||||
c.constraint_name
 | 
			
		||||
from
 | 
			
		||||
all_constraints a,
 | 
			
		||||
all_cons_columns c
 | 
			
		||||
where
 | 
			
		||||
a.constraint_name = c.constraint_name
 | 
			
		||||
and a.owner = c.owner
 | 
			
		||||
and a.table_name = c.table_name
 | 
			
		||||
and a.constraint_type in ('U')
 | 
			
		||||
and a.owner in ({0}) and a.table_name in ({1})", tboldname ?? tbname);
 | 
			
		||||
                    var dsuk = _orm.Ado.ExecuteArray(CommandType.Text, dsuksql).Select(a => new[] { string.Concat(a[0]), string.Concat(a[1]) });
 | 
			
		||||
                    foreach (var uk in tb.Uniques)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (string.IsNullOrEmpty(uk.Key) || uk.Value.Any() == false) continue;
 | 
			
		||||
                        var dsukfind1 = dsuk.Where(a => string.Compare(a[1], uk.Key, true) == 0).ToArray();
 | 
			
		||||
                        if (dsukfind1.Any() == false || dsukfind1.Length != uk.Value.Count || dsukfind1.Where(a => uk.Value.Where(b => string.Compare(b.Attribute.Name, a[0], true) == 0).Any()).Count() != uk.Value.Count)
 | 
			
		||||
                        {
 | 
			
		||||
                            if (dsukfind1.Any()) sbalter.Append("execute immediate 'ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" DROP CONSTRAINT ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append("';\r\n");
 | 
			
		||||
                            sbalter.Append("execute immediate 'ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" ADD CONSTRAINT ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append(" UNIQUE(");
 | 
			
		||||
                            foreach (var tbcol in uk.Value) sbalter.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", ");
 | 
			
		||||
                            sbalter.Remove(sbalter.Length - 2, 2).Append(")';\r\n");
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (istmpatler == false)
 | 
			
		||||
                {
 | 
			
		||||
                    sb.Append(sbalter);
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                var oldpk = _orm.Ado.ExecuteScalar(CommandType.Text, _commonUtils.FormatSql(@"select constraint_name from user_constraints where owner={0} and table_name={1} and constraint_type='P'", tbname))?.ToString();
 | 
			
		||||
                if (string.IsNullOrEmpty(oldpk) == false)
 | 
			
		||||
                    sb.Append("execute immediate 'ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" DROP CONSTRAINT ").Append(oldpk).Append("';\r\n");
 | 
			
		||||
 | 
			
		||||
                //创建临时表,数据导进临时表,然后删除原表,将临时表改名为原表名
 | 
			
		||||
                var tablename = tboldname == null ? _commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}") : _commonUtils.QuoteSqlName($"{tboldname[0]}.{tboldname[1]}");
 | 
			
		||||
                var tmptablename = _commonUtils.QuoteSqlName($"{tbname[0]}.FTmp_{tbname[1]}");
 | 
			
		||||
                //创建临时表
 | 
			
		||||
                sb.Append("execute immediate 'CREATE TABLE ").Append(tmptablename).Append(" ( ");
 | 
			
		||||
                foreach (var tbcol in tb.ColumnsByPosition)
 | 
			
		||||
                {
 | 
			
		||||
                    sb.Append(" \r\n  ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ").Append(tbcol.Attribute.DbType).Append(",");
 | 
			
		||||
                    if (tbcol.Attribute.IsIdentity == true) seqcols.Add((tbcol, tbname, true));
 | 
			
		||||
                }
 | 
			
		||||
                if (tb.Primarys.Any())
 | 
			
		||||
                {
 | 
			
		||||
                    var pkname = primaryKeyName ?? $"{tbname[0]}_{tbname[1]}_pk2";
 | 
			
		||||
                    sb.Append(" \r\n  CONSTRAINT ").Append(pkname).Append(" PRIMARY KEY (");
 | 
			
		||||
                    foreach (var tbcol in tb.Primarys) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", ");
 | 
			
		||||
                    sb.Remove(sb.Length - 2, 2).Append("),");
 | 
			
		||||
                }
 | 
			
		||||
                foreach (var uk in tb.Uniques)
 | 
			
		||||
                {
 | 
			
		||||
                    sb.Append(" \r\n  CONSTRAINT ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append(" UNIQUE (");
 | 
			
		||||
                    foreach (var tbcol in uk.Value) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", ");
 | 
			
		||||
                    sb.Remove(sb.Length - 2, 2).Append("),");
 | 
			
		||||
                }
 | 
			
		||||
                sb.Remove(sb.Length - 1, 1);
 | 
			
		||||
                sb.Append("\r\n) LOGGING \r\nNOCOMPRESS \r\nNOCACHE\r\n';\r\n");
 | 
			
		||||
                //备注
 | 
			
		||||
                foreach (var tbcol in tb.ColumnsByPosition)
 | 
			
		||||
                {
 | 
			
		||||
                    if (string.IsNullOrEmpty(tbcol.Comment) == false)
 | 
			
		||||
                        sb.Append("execute immediate 'COMMENT ON COLUMN ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.FTmp_{tbname[1]}.{tbcol.Attribute.Name}")).Append(" IS ").Append(_commonUtils.FormatSql("{0}", tbcol.Comment).Replace("'", "''")).Append("';\r\n");
 | 
			
		||||
                }
 | 
			
		||||
                sb.Append("execute immediate 'INSERT INTO ").Append(tmptablename).Append(" (");
 | 
			
		||||
                foreach (var tbcol in tb.ColumnsByPosition)
 | 
			
		||||
                    sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", ");
 | 
			
		||||
                sb.Remove(sb.Length - 2, 2).Append(")\r\nSELECT ");
 | 
			
		||||
                foreach (var tbcol in tb.ColumnsByPosition)
 | 
			
		||||
                {
 | 
			
		||||
                    var insertvalue = "NULL";
 | 
			
		||||
                    if (tbstruct.TryGetValue(tbcol.Attribute.Name, out var tbstructcol) ||
 | 
			
		||||
                        string.IsNullOrEmpty(tbcol.Attribute.OldName) == false && tbstruct.TryGetValue(tbcol.Attribute.OldName, out tbstructcol))
 | 
			
		||||
                    {
 | 
			
		||||
                        insertvalue = _commonUtils.QuoteSqlName(tbstructcol.column);
 | 
			
		||||
                        if (tbcol.Attribute.DbType.StartsWith(tbstructcol.sqlType, StringComparison.CurrentCultureIgnoreCase) == false)
 | 
			
		||||
                        {
 | 
			
		||||
                            var dbtypeNoneNotNull = Regex.Replace(tbcol.Attribute.DbType, @"(NOT\s+)?NULL", "");
 | 
			
		||||
                            insertvalue = $"cast({insertvalue} as {dbtypeNoneNotNull})";
 | 
			
		||||
                        }
 | 
			
		||||
                        if (tbcol.Attribute.IsNullable != tbstructcol.is_nullable)
 | 
			
		||||
                            insertvalue = $"nvl({insertvalue},{_commonUtils.FormatSql("{0}", tbcol.Attribute.DbDefautValue)})";
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (tbcol.Attribute.IsNullable == false)
 | 
			
		||||
                        insertvalue = _commonUtils.FormatSql("{0}", tbcol.Attribute.DbDefautValue);
 | 
			
		||||
                    sb.Append(insertvalue.Replace("'", "''")).Append(", ");
 | 
			
		||||
                }
 | 
			
		||||
                sb.Remove(sb.Length - 2, 2).Append(" FROM ").Append(tablename).Append("';\r\n");
 | 
			
		||||
                sb.Append("execute immediate 'DROP TABLE ").Append(tablename).Append("';\r\n");
 | 
			
		||||
                sb.Append("execute immediate 'ALTER TABLE ").Append(tmptablename).Append(" RENAME TO ").Append(_commonUtils.QuoteSqlName($"{tbname[1]}")).Append("';\r\n");
 | 
			
		||||
            }
 | 
			
		||||
            Dictionary<string, bool> dicDeclare = new Dictionary<string, bool>();
 | 
			
		||||
            Action<string> dropSequence = seqname =>
 | 
			
		||||
            {
 | 
			
		||||
                if (dicDeclare.ContainsKey(seqname) == false)
 | 
			
		||||
                {
 | 
			
		||||
                    sbDeclare.Append("\r\n").Append(seqname).Append("IS NUMBER; \r\n");
 | 
			
		||||
                    dicDeclare.Add(seqname, true);
 | 
			
		||||
                }
 | 
			
		||||
                sb.Append(seqname).Append("IS := 0; \r\n")
 | 
			
		||||
                    .Append(" select count(1) into ").Append(seqname).Append(_commonUtils.FormatSql("IS from user_sequences where sequence_name={0}; \r\n", seqname))
 | 
			
		||||
                    .Append("if ").Append(seqname).Append("IS > 0 then \r\n")
 | 
			
		||||
                    .Append("  execute immediate 'DROP SEQUENCE ").Append(_commonUtils.QuoteSqlName(seqname)).Append("';\r\n")
 | 
			
		||||
                    .Append("end if; \r\n");
 | 
			
		||||
            };
 | 
			
		||||
            Action<string> dropTrigger = tiggerName =>
 | 
			
		||||
            {
 | 
			
		||||
                if (dicDeclare.ContainsKey(tiggerName) == false)
 | 
			
		||||
                {
 | 
			
		||||
                    sbDeclare.Append("\r\n").Append(tiggerName).Append("IS NUMBER; \r\n");
 | 
			
		||||
                    dicDeclare.Add(tiggerName, true);
 | 
			
		||||
                }
 | 
			
		||||
                sb.Append(tiggerName).Append("IS := 0; \r\n")
 | 
			
		||||
                    .Append(" select count(1) into ").Append(tiggerName).Append(_commonUtils.FormatSql("IS from user_triggers where trigger_name={0}; \r\n", tiggerName))
 | 
			
		||||
                    .Append("if ").Append(tiggerName).Append("IS > 0 then \r\n")
 | 
			
		||||
                    .Append("  execute immediate 'DROP TRIGGER ").Append(_commonUtils.QuoteSqlName(tiggerName)).Append("';\r\n")
 | 
			
		||||
                    .Append("end if; \r\n");
 | 
			
		||||
            };
 | 
			
		||||
            foreach (var seqname in seqnameDel)
 | 
			
		||||
            {
 | 
			
		||||
                dropSequence(seqname);
 | 
			
		||||
                dropTrigger(seqname + "TI");
 | 
			
		||||
            }
 | 
			
		||||
            foreach (var seqcol in seqcols)
 | 
			
		||||
            {
 | 
			
		||||
                var tbname = seqcol.Item2;
 | 
			
		||||
                var seqname = Utils.GetCsName($"{tbname[1]}_seq_{seqcol.Item1.Attribute.Name}");
 | 
			
		||||
                var tiggerName = seqname + "TI";
 | 
			
		||||
                var tbname2 = _commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}");
 | 
			
		||||
                var colname2 = _commonUtils.QuoteSqlName(seqcol.Item1.Attribute.Name);
 | 
			
		||||
                dropSequence(seqname);
 | 
			
		||||
                if (seqcol.Item3)
 | 
			
		||||
                {
 | 
			
		||||
                    var startWith = _orm.Ado.ExecuteScalar(CommandType.Text, _commonUtils.FormatSql(" select 1 from all_tab_columns where owner={0} and table_name={1} and column_name={2}", tbname[0], tbname[1], colname2)) == null ? 1 :
 | 
			
		||||
                        _orm.Ado.ExecuteScalar(CommandType.Text, $" select nvl(max({colname2})+1,1) from {tbname2}");
 | 
			
		||||
                    sb.Append("execute immediate 'CREATE SEQUENCE ").Append(_commonUtils.QuoteSqlName(seqname)).Append(" start with ").Append(startWith).Append("';\r\n");
 | 
			
		||||
                    sb.Append("execute immediate 'CREATE OR REPLACE TRIGGER ").Append(_commonUtils.QuoteSqlName(tiggerName))
 | 
			
		||||
                        .Append(" \r\nbefore insert on ").Append(tbname2)
 | 
			
		||||
                        .Append(" \r\nfor each row \r\nbegin\r\nselect ").Append(_commonUtils.QuoteSqlName(seqname))
 | 
			
		||||
                        .Append(".nextval into :new.").Append(colname2).Append(" from dual;\r\nend;';\r\n");
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                    dropTrigger(tiggerName);
 | 
			
		||||
            }
 | 
			
		||||
            if (sbDeclare.Length > 0) sbDeclare.Insert(0, "declare ");
 | 
			
		||||
            return sb.Length == 0 ? null : sb.Insert(0, "BEGIN \r\n").Insert(0, sbDeclare.ToString()).Append("END;").ToString();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        internal static string GetOracleSqlTypeFullName(object[] row)
 | 
			
		||||
        {
 | 
			
		||||
            var a = row;
 | 
			
		||||
            var sqlType = string.Concat(a[1]).ToUpper();
 | 
			
		||||
            var data_length = long.Parse(string.Concat(a[2]));
 | 
			
		||||
            long.TryParse(string.Concat(a[3]), out var data_precision);
 | 
			
		||||
            long.TryParse(string.Concat(a[4]), out var data_scale);
 | 
			
		||||
            var char_used = string.Concat(a[5]);
 | 
			
		||||
            if (Regex.IsMatch(sqlType, @"INTERVAL DAY\(\d+\) TO SECOND\(\d+\)", RegexOptions.IgnoreCase))
 | 
			
		||||
            {
 | 
			
		||||
            }
 | 
			
		||||
            else if (Regex.IsMatch(sqlType, @"INTERVAL YEAR\(\d+\) TO MONTH", RegexOptions.IgnoreCase))
 | 
			
		||||
            {
 | 
			
		||||
            }
 | 
			
		||||
            else if (sqlType.StartsWith("TIMESTAMP", StringComparison.CurrentCultureIgnoreCase))
 | 
			
		||||
            {
 | 
			
		||||
            }
 | 
			
		||||
            else if (sqlType.StartsWith("BLOB"))
 | 
			
		||||
            {
 | 
			
		||||
            }
 | 
			
		||||
            else if (char_used.ToLower() == "c")
 | 
			
		||||
                sqlType += sqlType.StartsWith("N") ? $"({data_length / 2})" : $"({data_length / 4} CHAR)";
 | 
			
		||||
            else if (char_used.ToLower() == "b")
 | 
			
		||||
                sqlType += $"({data_length} BYTE)";
 | 
			
		||||
            else if (sqlType.ToLower() == "float")
 | 
			
		||||
                sqlType += $"({data_precision})";
 | 
			
		||||
            else if (data_precision > 0 && data_scale > 0)
 | 
			
		||||
                sqlType += $"({data_precision},{data_scale})";
 | 
			
		||||
            else if (data_precision > 0)
 | 
			
		||||
                sqlType += $"({data_precision})";
 | 
			
		||||
            else
 | 
			
		||||
                sqlType += $"({data_length})";
 | 
			
		||||
            return sqlType;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										484
									
								
								Providers/FreeSql.Provider.Odbc/Oracle/OdbcOracleDbFirst.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										484
									
								
								Providers/FreeSql.Provider.Odbc/Oracle/OdbcOracleDbFirst.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,484 @@
 | 
			
		||||
using FreeSql.DatabaseModel;
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data;
 | 
			
		||||
using System.Data.Odbc;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.Oracle
 | 
			
		||||
{
 | 
			
		||||
    class OdbcOracleDbFirst : IDbFirst
 | 
			
		||||
    {
 | 
			
		||||
        IFreeSql _orm;
 | 
			
		||||
        protected CommonUtils _commonUtils;
 | 
			
		||||
        protected CommonExpression _commonExpression;
 | 
			
		||||
        public OdbcOracleDbFirst(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression)
 | 
			
		||||
        {
 | 
			
		||||
            _orm = orm;
 | 
			
		||||
            _commonUtils = commonUtils;
 | 
			
		||||
            _commonExpression = commonExpression;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public int GetDbType(DbColumnInfo column) => (int)GetSqlDbType(column);
 | 
			
		||||
        OdbcType GetSqlDbType(DbColumnInfo column)
 | 
			
		||||
        {
 | 
			
		||||
            var dbfull = column.DbTypeTextFull.ToLower();
 | 
			
		||||
            switch (dbfull)
 | 
			
		||||
            {
 | 
			
		||||
                case "number(1)": return OdbcType.Bit;
 | 
			
		||||
 | 
			
		||||
                case "number(4)": return OdbcType.SmallInt;
 | 
			
		||||
                case "number(6)": return OdbcType.SmallInt;
 | 
			
		||||
                case "number(11)": return OdbcType.Int;
 | 
			
		||||
                case "number(21)": return OdbcType.BigInt;
 | 
			
		||||
 | 
			
		||||
                case "number(3)": return OdbcType.TinyInt;
 | 
			
		||||
                case "number(5)": return OdbcType.SmallInt;
 | 
			
		||||
                case "number(10)": return OdbcType.BigInt;
 | 
			
		||||
                case "number(20)": return OdbcType.Decimal;
 | 
			
		||||
 | 
			
		||||
                case "float(126)": return OdbcType.Double;
 | 
			
		||||
                case "float(63)": return OdbcType.Real;
 | 
			
		||||
                case "number(10,2)": return OdbcType.Decimal;
 | 
			
		||||
 | 
			
		||||
                case "interval day(2) to second(6)": return OdbcType.Time;
 | 
			
		||||
                case "timestamp(6)": return OdbcType.DateTime;
 | 
			
		||||
                case "timestamp(6) with local time zone": return OdbcType.DateTime;
 | 
			
		||||
 | 
			
		||||
                case "blob": return OdbcType.VarBinary;
 | 
			
		||||
                case "nvarchar2(255)": return OdbcType.NVarChar;
 | 
			
		||||
 | 
			
		||||
                case "char(36 char)": return OdbcType.Char;
 | 
			
		||||
            }
 | 
			
		||||
            switch (column.DbTypeText.ToLower())
 | 
			
		||||
            {
 | 
			
		||||
                case "number":
 | 
			
		||||
                    _dicDbToCs.TryAdd(dbfull, _dicDbToCs["number(10,2)"]);
 | 
			
		||||
                    return OdbcType.Decimal;
 | 
			
		||||
                case "float":
 | 
			
		||||
                    _dicDbToCs.TryAdd(dbfull, _dicDbToCs["float(126)"]);
 | 
			
		||||
                    return OdbcType.Double;
 | 
			
		||||
                case "interval day to second":
 | 
			
		||||
                    _dicDbToCs.TryAdd(dbfull, _dicDbToCs["interval day(2) to second(6)"]);
 | 
			
		||||
                    return OdbcType.Time;
 | 
			
		||||
                case "date":
 | 
			
		||||
                    _dicDbToCs.TryAdd(dbfull, _dicDbToCs["date(7)"]);
 | 
			
		||||
                    return OdbcType.DateTime;
 | 
			
		||||
                case "timestamp":
 | 
			
		||||
                    _dicDbToCs.TryAdd(dbfull, _dicDbToCs["timestamp(6)"]);
 | 
			
		||||
                    return OdbcType.DateTime;
 | 
			
		||||
                case "timestamp with local time zone":
 | 
			
		||||
                    _dicDbToCs.TryAdd(dbfull, _dicDbToCs["timestamp(6) with local time zone"]);
 | 
			
		||||
                    return OdbcType.DateTime;
 | 
			
		||||
                case "blob":
 | 
			
		||||
                    _dicDbToCs.TryAdd(dbfull, _dicDbToCs["blob"]);
 | 
			
		||||
                    return OdbcType.VarBinary;
 | 
			
		||||
                case "nvarchar2":
 | 
			
		||||
                    _dicDbToCs.TryAdd(dbfull, _dicDbToCs["nvarchar2(255)"]);
 | 
			
		||||
                    return OdbcType.NVarChar;
 | 
			
		||||
                case "varchar2":
 | 
			
		||||
                    _dicDbToCs.TryAdd(dbfull, _dicDbToCs["nvarchar2(255)"]);
 | 
			
		||||
                    return OdbcType.NVarChar;
 | 
			
		||||
                case "char":
 | 
			
		||||
                    _dicDbToCs.TryAdd(dbfull, _dicDbToCs["nvarchar2(255)"]);
 | 
			
		||||
                    return OdbcType.Char;
 | 
			
		||||
                case "nchar":
 | 
			
		||||
                    _dicDbToCs.TryAdd(dbfull, _dicDbToCs["nvarchar2(255)"]);
 | 
			
		||||
                    return OdbcType.NChar;
 | 
			
		||||
                case "clob":
 | 
			
		||||
                    _dicDbToCs.TryAdd(dbfull, _dicDbToCs["nvarchar2(255)"]);
 | 
			
		||||
                    return OdbcType.NVarChar;
 | 
			
		||||
                case "nclob":
 | 
			
		||||
                    _dicDbToCs.TryAdd(dbfull, _dicDbToCs["nvarchar2(255)"]);
 | 
			
		||||
                    return OdbcType.NVarChar;
 | 
			
		||||
                case "raw":
 | 
			
		||||
                    _dicDbToCs.TryAdd(dbfull, _dicDbToCs["blob"]);
 | 
			
		||||
                    return OdbcType.VarBinary;
 | 
			
		||||
                case "long raw":
 | 
			
		||||
                    _dicDbToCs.TryAdd(dbfull, _dicDbToCs["blob"]);
 | 
			
		||||
                    return OdbcType.VarBinary;
 | 
			
		||||
                case "binary_float":
 | 
			
		||||
                    _dicDbToCs.TryAdd(dbfull, _dicDbToCs["float(63)"]);
 | 
			
		||||
                    return OdbcType.Real;
 | 
			
		||||
                case "binary_double":
 | 
			
		||||
                    _dicDbToCs.TryAdd(dbfull, _dicDbToCs["float(126)"]);
 | 
			
		||||
                    return OdbcType.Double;
 | 
			
		||||
                case "rowid":
 | 
			
		||||
                default:
 | 
			
		||||
                    _dicDbToCs.TryAdd(dbfull, _dicDbToCs["nvarchar2(255)"]);
 | 
			
		||||
                    return OdbcType.NVarChar;
 | 
			
		||||
            }
 | 
			
		||||
            throw new NotImplementedException($"未实现 {column.DbTypeTextFull} 类型映射");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        static ConcurrentDictionary<string, (string csConvert, string csParse, string csStringify, string csType, Type csTypeInfo, Type csNullableTypeInfo, string csTypeValue, string dataReaderMethod)> _dicDbToCs = new ConcurrentDictionary<string, (string csConvert, string csParse, string csStringify, string csType, Type csTypeInfo, Type csNullableTypeInfo, string csTypeValue, string dataReaderMethod)>(StringComparer.CurrentCultureIgnoreCase);
 | 
			
		||||
        static OdbcOracleDbFirst()
 | 
			
		||||
        {
 | 
			
		||||
            var defaultDbToCs = new Dictionary<string, (string csConvert, string csParse, string csStringify, string csType, Type csTypeInfo, Type csNullableTypeInfo, string csTypeValue, string dataReaderMethod)>() {
 | 
			
		||||
                { "number(1)", ("(bool?)", "{0} == \"1\"", "{0} == true ? \"1\" : \"0\"", "bool?", typeof(bool), typeof(bool?), "{0}.Value", "GetBoolean") },
 | 
			
		||||
 | 
			
		||||
                { "number(4)", ("(sbyte?)", "sbyte.Parse({0})", "{0}.ToString()", "sbyte?", typeof(sbyte), typeof(sbyte?), "{0}.Value", "GetInt16") },
 | 
			
		||||
                { "number(6)", ("(short?)", "short.Parse({0})", "{0}.ToString()", "short?", typeof(short), typeof(short?), "{0}.Value", "GetInt16") },
 | 
			
		||||
                { "number(11)", ("(int?)", "int.Parse({0})", "{0}.ToString()", "int?", typeof(int), typeof(int?), "{0}.Value", "GetInt32") },
 | 
			
		||||
                { "number(21)", ("(long?)", "long.Parse({0})", "{0}.ToString()", "long?", typeof(long), typeof(long?), "{0}.Value", "GetInt64") },
 | 
			
		||||
 | 
			
		||||
                { "number(3)", ("(byte?)", "byte.Parse({0})", "{0}.ToString()", "byte?", typeof(byte), typeof(byte?), "{0}.Value", "GetByte") },
 | 
			
		||||
                { "number(5)", ("(ushort?)", "ushort.Parse({0})", "{0}.ToString()", "ushort?", typeof(ushort), typeof(ushort?), "{0}.Value", "GetInt32") },
 | 
			
		||||
                { "number(10)", ("(uint?)", "uint.Parse({0})", "{0}.ToString()", "uint?", typeof(uint), typeof(uint?), "{0}.Value", "GetInt64") },
 | 
			
		||||
                { "number(20)", ("(ulong?)", "ulong.Parse({0})", "{0}.ToString()", "ulong?", typeof(ulong), typeof(ulong?), "{0}.Value", "GetDecimal") },
 | 
			
		||||
 | 
			
		||||
                { "float(126)", ("(double?)", "double.Parse({0})", "{0}.ToString()", "double?", typeof(double), typeof(double?), "{0}.Value", "GetDouble") },
 | 
			
		||||
                { "float(63)", ("(float?)", "float.Parse({0})", "{0}.ToString()", "float?", typeof(float), typeof(float?), "{0}.Value", "GetFloat") },
 | 
			
		||||
                { "number(10,2)", ("(decimal?)", "decimal.Parse({0})", "{0}.ToString()", "decimal?", typeof(decimal), typeof(decimal?), "{0}.Value", "GetDecimal") },
 | 
			
		||||
 | 
			
		||||
                { "interval day(2) to second(6)", ("(TimeSpan?)", "TimeSpan.Parse(double.Parse({0}))", "{0}.Ticks.ToString()", "TimeSpan?", typeof(TimeSpan), typeof(TimeSpan?), "{0}.Value", "GetValue") },
 | 
			
		||||
                { "date(7)", ("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetValue") },
 | 
			
		||||
                { "timestamp(6)", ("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetValue") },
 | 
			
		||||
                { "timestamp(6) with local time zone", ("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetValue") },
 | 
			
		||||
 | 
			
		||||
                { "blob", ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", typeof(byte[]), typeof(byte[]), "{0}", "GetValue") },
 | 
			
		||||
 | 
			
		||||
                { "nvarchar2(255)", ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") },
 | 
			
		||||
                { "char(36 char)", ("(Guid?)", "Guid.Parse({0})", "{0}.ToString()", "Guid?", typeof(Guid), typeof(Guid?), "{0}.Value", "GetGuid") },
 | 
			
		||||
            };
 | 
			
		||||
            foreach (var kv in defaultDbToCs)
 | 
			
		||||
                _dicDbToCs.TryAdd(kv.Key, kv.Value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        public string GetCsConvert(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbTypeTextFull, out var trydc) ? (column.IsNullable ? trydc.csConvert : trydc.csConvert.Replace("?", "")) : null;
 | 
			
		||||
        public string GetCsParse(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbTypeTextFull, out var trydc) ? trydc.csParse : null;
 | 
			
		||||
        public string GetCsStringify(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbTypeTextFull, out var trydc) ? trydc.csStringify : null;
 | 
			
		||||
        public string GetCsType(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbTypeTextFull, out var trydc) ? (column.IsNullable ? trydc.csType : trydc.csType.Replace("?", "")) : null;
 | 
			
		||||
        public Type GetCsTypeInfo(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbTypeTextFull, out var trydc) ? trydc.csTypeInfo : null;
 | 
			
		||||
        public string GetCsTypeValue(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbTypeTextFull, out var trydc) ? trydc.csTypeValue : null;
 | 
			
		||||
        public string GetDataReaderMethod(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbTypeTextFull, out var trydc) ? trydc.dataReaderMethod : null;
 | 
			
		||||
 | 
			
		||||
        public List<string> GetDatabases()
 | 
			
		||||
        {
 | 
			
		||||
            var sql = @" select username from all_users";
 | 
			
		||||
            var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql);
 | 
			
		||||
            return ds.Select(a => a.FirstOrDefault()?.ToString()).ToList();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public List<DbTableInfo> GetTablesByDatabase(params string[] database2)
 | 
			
		||||
        {
 | 
			
		||||
            var loc1 = new List<DbTableInfo>();
 | 
			
		||||
            var loc2 = new Dictionary<string, DbTableInfo>();
 | 
			
		||||
            var loc3 = new Dictionary<string, Dictionary<string, DbColumnInfo>>();
 | 
			
		||||
            var database = database2?.ToArray();
 | 
			
		||||
 | 
			
		||||
            if (database == null || database.Any() == false)
 | 
			
		||||
            {
 | 
			
		||||
                var userUsers = _orm.Ado.ExecuteScalar("select username from user_users")?.ToString();
 | 
			
		||||
                if (string.IsNullOrEmpty(userUsers)) return loc1;
 | 
			
		||||
                database = new[] { userUsers };
 | 
			
		||||
            }
 | 
			
		||||
            var databaseIn = string.Join(",", database.Select(a => _commonUtils.FormatSql("{0}", a)));
 | 
			
		||||
            var sql = string.Format(@"
 | 
			
		||||
select
 | 
			
		||||
a.owner || '.' || a.table_name,
 | 
			
		||||
a.owner,
 | 
			
		||||
a.table_name,
 | 
			
		||||
b.comments,
 | 
			
		||||
'TABLE'
 | 
			
		||||
from all_tables a
 | 
			
		||||
left join all_tab_comments b on b.owner = a.owner and b.table_name = a.table_name and b.table_type = 'TABLE'
 | 
			
		||||
where a.owner in ({0})", databaseIn);
 | 
			
		||||
            var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql);
 | 
			
		||||
            if (ds == null) return loc1;
 | 
			
		||||
 | 
			
		||||
            var loc6 = new List<string>();
 | 
			
		||||
            var loc66 = new List<string>();
 | 
			
		||||
            foreach (var row in ds)
 | 
			
		||||
            {
 | 
			
		||||
                var table_id = string.Concat(row[0]);
 | 
			
		||||
                var schema = string.Concat(row[1]);
 | 
			
		||||
                var table = string.Concat(row[2]);
 | 
			
		||||
                var comment = string.Concat(row[3]);
 | 
			
		||||
                var type = string.Concat(row[4]) == "VIEW" ? DbTableType.VIEW : DbTableType.TABLE;
 | 
			
		||||
                if (database.Length == 1)
 | 
			
		||||
                {
 | 
			
		||||
                    table_id = table_id.Substring(table_id.IndexOf('.') + 1);
 | 
			
		||||
                    schema = "";
 | 
			
		||||
                }
 | 
			
		||||
                loc2.Add(table_id, new DbTableInfo { Id = table_id, Schema = schema, Name = table, Comment = comment, Type = type });
 | 
			
		||||
                loc3.Add(table_id, new Dictionary<string, DbColumnInfo>());
 | 
			
		||||
                switch (type)
 | 
			
		||||
                {
 | 
			
		||||
                    case DbTableType.TABLE:
 | 
			
		||||
                    case DbTableType.VIEW:
 | 
			
		||||
                        loc6.Add(table.Replace("'", "''"));
 | 
			
		||||
                        break;
 | 
			
		||||
                    case DbTableType.StoreProcedure:
 | 
			
		||||
                        loc66.Add(table.Replace("'", "''"));
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (loc6.Count == 0) return loc1;
 | 
			
		||||
            var loc8 = "'" + string.Join("','", loc6.ToArray()) + "'";
 | 
			
		||||
            var loc88 = "'" + string.Join("','", loc66.ToArray()) + "'";
 | 
			
		||||
 | 
			
		||||
            sql = string.Format(@"
 | 
			
		||||
select
 | 
			
		||||
a.owner || '.' || a.table_name,
 | 
			
		||||
a.column_name,
 | 
			
		||||
a.data_type,
 | 
			
		||||
a.data_length,
 | 
			
		||||
a.data_precision,
 | 
			
		||||
a.data_scale,
 | 
			
		||||
a.char_used,
 | 
			
		||||
case when a.nullable = 'Y' then 1 else 0 end,
 | 
			
		||||
nvl((select 1 from user_sequences where upper(sequence_name)=upper(a.table_name||'_seq_'||a.column_name)), 0),
 | 
			
		||||
b.comments
 | 
			
		||||
from all_tab_cols a
 | 
			
		||||
left join all_col_comments b on b.owner = a.owner and b.table_name = a.table_name and b.column_name = a.column_name
 | 
			
		||||
where a.owner in ({1}) and a.table_name in ({0})
 | 
			
		||||
", loc8, databaseIn);
 | 
			
		||||
            ds = _orm.Ado.ExecuteArray(CommandType.Text, sql);
 | 
			
		||||
            if (ds == null) return loc1;
 | 
			
		||||
 | 
			
		||||
            var ds2 = new List<object[]>();
 | 
			
		||||
            foreach (var row in ds)
 | 
			
		||||
            {
 | 
			
		||||
                var ds2item = new object[8];
 | 
			
		||||
                ds2item[0] = row[0];
 | 
			
		||||
                ds2item[1] = row[1];
 | 
			
		||||
                ds2item[2] = Regex.Replace(string.Concat(row[2]), @"\(\d+\)", "");
 | 
			
		||||
                ds2item[4] = OdbcOracleCodeFirst.GetOracleSqlTypeFullName(new object[] { row[1], row[2], row[3], row[4], row[5], row[6] });
 | 
			
		||||
                ds2item[5] = string.Concat(row[7]) == "1";
 | 
			
		||||
                ds2item[6] = string.Concat(row[8]) == "1";
 | 
			
		||||
                ds2item[7] = string.Concat(row[9]);
 | 
			
		||||
                ds2.Add(ds2item);
 | 
			
		||||
            }
 | 
			
		||||
            foreach (var row in ds2)
 | 
			
		||||
            {
 | 
			
		||||
                string table_id = string.Concat(row[0]);
 | 
			
		||||
                string column = string.Concat(row[1]);
 | 
			
		||||
                string type = string.Concat(row[2]);
 | 
			
		||||
                //long max_length = long.Parse(string.Concat(row[3]));
 | 
			
		||||
                string sqlType = string.Concat(row[4]);
 | 
			
		||||
                var m_len = Regex.Match(sqlType, @"\w+\((\d+)");
 | 
			
		||||
                int max_length = m_len.Success ? int.Parse(m_len.Groups[1].Value) : -1;
 | 
			
		||||
                bool is_nullable = string.Concat(row[5]) == "1";
 | 
			
		||||
                bool is_identity = string.Concat(row[6]) == "1";
 | 
			
		||||
                string comment = string.Concat(row[7]);
 | 
			
		||||
                if (max_length == 0) max_length = -1;
 | 
			
		||||
                if (database.Length == 1)
 | 
			
		||||
                {
 | 
			
		||||
                    table_id = table_id.Substring(table_id.IndexOf('.') + 1);
 | 
			
		||||
                }
 | 
			
		||||
                loc3[table_id].Add(column, new DbColumnInfo
 | 
			
		||||
                {
 | 
			
		||||
                    Name = column,
 | 
			
		||||
                    MaxLength = max_length,
 | 
			
		||||
                    IsIdentity = is_identity,
 | 
			
		||||
                    IsNullable = is_nullable,
 | 
			
		||||
                    IsPrimary = false,
 | 
			
		||||
                    DbTypeText = type,
 | 
			
		||||
                    DbTypeTextFull = sqlType,
 | 
			
		||||
                    Table = loc2[table_id],
 | 
			
		||||
                    Coment = comment
 | 
			
		||||
                });
 | 
			
		||||
                loc3[table_id][column].DbType = this.GetDbType(loc3[table_id][column]);
 | 
			
		||||
                loc3[table_id][column].CsType = this.GetCsTypeInfo(loc3[table_id][column]);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            sql = string.Format(@"
 | 
			
		||||
select
 | 
			
		||||
a.owner || '.' || a.table_name,
 | 
			
		||||
c.column_name,
 | 
			
		||||
c.constraint_name,
 | 
			
		||||
case when a.constraint_type = 'U' then 1 else 0 end,
 | 
			
		||||
case when a.constraint_type = 'P' then 1 else 0 end,
 | 
			
		||||
0,
 | 
			
		||||
0
 | 
			
		||||
from
 | 
			
		||||
all_constraints a,
 | 
			
		||||
all_cons_columns c
 | 
			
		||||
where
 | 
			
		||||
a.constraint_name = c.constraint_name
 | 
			
		||||
and a.owner = c.owner
 | 
			
		||||
and a.table_name = c.table_name
 | 
			
		||||
and a.constraint_type  in ('P', 'U')
 | 
			
		||||
and a.owner in ({1}) and a.table_name in ({0})
 | 
			
		||||
", loc8, databaseIn);
 | 
			
		||||
            ds = _orm.Ado.ExecuteArray(CommandType.Text, sql);
 | 
			
		||||
            if (ds == null) return loc1;
 | 
			
		||||
 | 
			
		||||
            var indexColumns = new Dictionary<string, Dictionary<string, List<DbColumnInfo>>>();
 | 
			
		||||
            var uniqueColumns = new Dictionary<string, Dictionary<string, List<DbColumnInfo>>>();
 | 
			
		||||
            foreach (var row in ds)
 | 
			
		||||
            {
 | 
			
		||||
                string table_id = string.Concat(row[0]);
 | 
			
		||||
                string column = string.Concat(row[1]);
 | 
			
		||||
                string index_id = string.Concat(row[2]);
 | 
			
		||||
                bool is_unique = string.Concat(row[3]) == "1";
 | 
			
		||||
                bool is_primary_key = string.Concat(row[4]) == "1";
 | 
			
		||||
                bool is_clustered = string.Concat(row[5]) == "1";
 | 
			
		||||
                int is_desc = int.Parse(string.Concat(row[6]));
 | 
			
		||||
                if (database.Length == 1)
 | 
			
		||||
                {
 | 
			
		||||
                    table_id = table_id.Substring(table_id.IndexOf('.') + 1);
 | 
			
		||||
                }
 | 
			
		||||
                if (loc3.ContainsKey(table_id) == false || loc3[table_id].ContainsKey(column) == false) continue;
 | 
			
		||||
                var loc9 = loc3[table_id][column];
 | 
			
		||||
                if (loc9.IsPrimary == false && is_primary_key) loc9.IsPrimary = is_primary_key;
 | 
			
		||||
 | 
			
		||||
                Dictionary<string, List<DbColumnInfo>> loc10 = null;
 | 
			
		||||
                List<DbColumnInfo> loc11 = null;
 | 
			
		||||
                if (!indexColumns.TryGetValue(table_id, out loc10))
 | 
			
		||||
                    indexColumns.Add(table_id, loc10 = new Dictionary<string, List<DbColumnInfo>>());
 | 
			
		||||
                if (!loc10.TryGetValue(index_id, out loc11))
 | 
			
		||||
                    loc10.Add(index_id, loc11 = new List<DbColumnInfo>());
 | 
			
		||||
                loc11.Add(loc9);
 | 
			
		||||
                if (is_unique && !is_primary_key)
 | 
			
		||||
                {
 | 
			
		||||
                    if (!uniqueColumns.TryGetValue(table_id, out loc10))
 | 
			
		||||
                        uniqueColumns.Add(table_id, loc10 = new Dictionary<string, List<DbColumnInfo>>());
 | 
			
		||||
                    if (!loc10.TryGetValue(index_id, out loc11))
 | 
			
		||||
                        loc10.Add(index_id, loc11 = new List<DbColumnInfo>());
 | 
			
		||||
                    loc11.Add(loc9);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            foreach (string table_id in indexColumns.Keys)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var column in indexColumns[table_id])
 | 
			
		||||
                    loc2[table_id].IndexesDict.Add(column.Key, column.Value);
 | 
			
		||||
            }
 | 
			
		||||
            foreach (string table_id in uniqueColumns.Keys)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var column in uniqueColumns[table_id])
 | 
			
		||||
                {
 | 
			
		||||
                    column.Value.Sort((c1, c2) => c1.Name.CompareTo(c2.Name));
 | 
			
		||||
                    loc2[table_id].UniquesDict.Add(column.Key, column.Value);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            sql = string.Format(@"
 | 
			
		||||
select
 | 
			
		||||
a.owner || '.' || a.table_name,
 | 
			
		||||
c.column_name,
 | 
			
		||||
c.constraint_name,
 | 
			
		||||
b.owner || '.' || b.table_name,
 | 
			
		||||
1,
 | 
			
		||||
d.column_name
 | 
			
		||||
 | 
			
		||||
-- a.owner 外键拥有者,
 | 
			
		||||
-- a.table_name 外键表,
 | 
			
		||||
-- c.column_name 外键列,
 | 
			
		||||
-- b.owner 主键拥有者,
 | 
			
		||||
-- b.table_name 主键表,
 | 
			
		||||
-- d.column_name 主键列,
 | 
			
		||||
-- c.constraint_name 外键名,
 | 
			
		||||
-- d.constraint_name 主键名
 | 
			
		||||
 | 
			
		||||
from
 | 
			
		||||
all_constraints a,
 | 
			
		||||
all_constraints b,
 | 
			
		||||
all_cons_columns c, --外键表
 | 
			
		||||
all_cons_columns d  --主键表
 | 
			
		||||
where
 | 
			
		||||
a.r_constraint_name = b.constraint_name   
 | 
			
		||||
and a.constraint_type = 'R'   
 | 
			
		||||
and b.constraint_type = 'P'   
 | 
			
		||||
and a.r_owner = b.owner   
 | 
			
		||||
and a.constraint_name = c.constraint_name   
 | 
			
		||||
and b.constraint_name = d.constraint_name   
 | 
			
		||||
and a.owner = c.owner   
 | 
			
		||||
and a.table_name = c.table_name   
 | 
			
		||||
and b.owner = d.owner   
 | 
			
		||||
and b.table_name = d.table_name
 | 
			
		||||
and a.owner in ({1}) and a.table_name in ({0})
 | 
			
		||||
", loc8, databaseIn);
 | 
			
		||||
            ds = _orm.Ado.ExecuteArray(CommandType.Text, sql);
 | 
			
		||||
            if (ds == null) return loc1;
 | 
			
		||||
 | 
			
		||||
            var fkColumns = new Dictionary<string, Dictionary<string, DbForeignInfo>>();
 | 
			
		||||
            foreach (var row in ds)
 | 
			
		||||
            {
 | 
			
		||||
                string table_id = string.Concat(row[0]);
 | 
			
		||||
                string column = string.Concat(row[1]);
 | 
			
		||||
                string fk_id = string.Concat(row[2]);
 | 
			
		||||
                string ref_table_id = string.Concat(row[3]);
 | 
			
		||||
                bool is_foreign_key = string.Concat(row[4]) == "1";
 | 
			
		||||
                string referenced_column = string.Concat(row[5]);
 | 
			
		||||
                if (database.Length == 1)
 | 
			
		||||
                {
 | 
			
		||||
                    table_id = table_id.Substring(table_id.IndexOf('.') + 1);
 | 
			
		||||
                    ref_table_id = ref_table_id.Substring(ref_table_id.IndexOf('.') + 1);
 | 
			
		||||
                }
 | 
			
		||||
                if (loc3.ContainsKey(table_id) == false || loc3[table_id].ContainsKey(column) == false) continue;
 | 
			
		||||
                var loc9 = loc3[table_id][column];
 | 
			
		||||
                if (loc2.ContainsKey(ref_table_id) == false) continue;
 | 
			
		||||
                var loc10 = loc2[ref_table_id];
 | 
			
		||||
                var loc11 = loc3[ref_table_id][referenced_column];
 | 
			
		||||
 | 
			
		||||
                Dictionary<string, DbForeignInfo> loc12 = null;
 | 
			
		||||
                DbForeignInfo loc13 = null;
 | 
			
		||||
                if (!fkColumns.TryGetValue(table_id, out loc12))
 | 
			
		||||
                    fkColumns.Add(table_id, loc12 = new Dictionary<string, DbForeignInfo>());
 | 
			
		||||
                if (!loc12.TryGetValue(fk_id, out loc13))
 | 
			
		||||
                    loc12.Add(fk_id, loc13 = new DbForeignInfo { Table = loc2[table_id], ReferencedTable = loc10 });
 | 
			
		||||
                loc13.Columns.Add(loc9);
 | 
			
		||||
                loc13.ReferencedColumns.Add(loc11);
 | 
			
		||||
            }
 | 
			
		||||
            foreach (var table_id in fkColumns.Keys)
 | 
			
		||||
                foreach (var fk in fkColumns[table_id])
 | 
			
		||||
                    loc2[table_id].ForeignsDict.Add(fk.Key, fk.Value);
 | 
			
		||||
 | 
			
		||||
            foreach (var table_id in loc3.Keys)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var loc5 in loc3[table_id].Values)
 | 
			
		||||
                {
 | 
			
		||||
                    loc2[table_id].Columns.Add(loc5);
 | 
			
		||||
                    if (loc5.IsIdentity) loc2[table_id].Identitys.Add(loc5);
 | 
			
		||||
                    if (loc5.IsPrimary) loc2[table_id].Primarys.Add(loc5);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            foreach (var loc4 in loc2.Values)
 | 
			
		||||
            {
 | 
			
		||||
                if (loc4.Primarys.Count == 0 && loc4.UniquesDict.Count > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    foreach (var loc5 in loc4.UniquesDict.First().Value)
 | 
			
		||||
                    {
 | 
			
		||||
                        loc5.IsPrimary = true;
 | 
			
		||||
                        loc4.Primarys.Add(loc5);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                loc4.Primarys.Sort((c1, c2) => c1.Name.CompareTo(c2.Name));
 | 
			
		||||
                loc4.Columns.Sort((c1, c2) =>
 | 
			
		||||
                {
 | 
			
		||||
                    int compare = c2.IsPrimary.CompareTo(c1.IsPrimary);
 | 
			
		||||
                    if (compare == 0)
 | 
			
		||||
                    {
 | 
			
		||||
                        bool b1 = loc4.ForeignsDict.Values.Where(fk => fk.Columns.Where(c3 => c3.Name == c1.Name).Any()).Any();
 | 
			
		||||
                        bool b2 = loc4.ForeignsDict.Values.Where(fk => fk.Columns.Where(c3 => c3.Name == c2.Name).Any()).Any();
 | 
			
		||||
                        compare = b2.CompareTo(b1);
 | 
			
		||||
                    }
 | 
			
		||||
                    if (compare == 0) compare = c1.Name.CompareTo(c2.Name);
 | 
			
		||||
                    return compare;
 | 
			
		||||
                });
 | 
			
		||||
                loc1.Add(loc4);
 | 
			
		||||
            }
 | 
			
		||||
            loc1.Sort((t1, t2) =>
 | 
			
		||||
            {
 | 
			
		||||
                var ret = t1.Schema.CompareTo(t2.Schema);
 | 
			
		||||
                if (ret == 0) ret = t1.Name.CompareTo(t2.Name);
 | 
			
		||||
                return ret;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            loc2.Clear();
 | 
			
		||||
            loc3.Clear();
 | 
			
		||||
            return loc1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public List<DbEnumInfo> GetEnumsByDatabase(params string[] database)
 | 
			
		||||
        {
 | 
			
		||||
            return new List<DbEnumInfo>();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										458
									
								
								Providers/FreeSql.Provider.Odbc/Oracle/OdbcOracleExpression.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										458
									
								
								Providers/FreeSql.Provider.Odbc/Oracle/OdbcOracleExpression.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,458 @@
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using FreeSql.Internal.Model;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Linq.Expressions;
 | 
			
		||||
using System.Text;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.Oracle
 | 
			
		||||
{
 | 
			
		||||
    class OdbcOracleExpression : CommonExpression
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        public OdbcOracleExpression(CommonUtils common) : base(common) { }
 | 
			
		||||
 | 
			
		||||
        public override string ExpressionLambdaToSqlOther(Expression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            Func<Expression, string> getExp = exparg => ExpressionLambdaToSql(exparg, tsc);
 | 
			
		||||
            switch (exp.NodeType)
 | 
			
		||||
            {
 | 
			
		||||
                case ExpressionType.Convert:
 | 
			
		||||
                    var operandExp = (exp as UnaryExpression)?.Operand;
 | 
			
		||||
                    var gentype = exp.Type.NullableTypeOrThis();
 | 
			
		||||
                    if (gentype != operandExp.Type.NullableTypeOrThis())
 | 
			
		||||
                    {
 | 
			
		||||
                        switch (exp.Type.NullableTypeOrThis().ToString())
 | 
			
		||||
                        {
 | 
			
		||||
                            //case "System.Boolean": return $"({getExp(operandExp)} not in ('0','false'))";
 | 
			
		||||
                            case "System.Byte": return $"cast({getExp(operandExp)} as number)";
 | 
			
		||||
                            case "System.Char": return $"substr(to_char({getExp(operandExp)}), 1, 1)";
 | 
			
		||||
                            case "System.DateTime": return $"to_timestamp({getExp(operandExp)},'YYYY-MM-DD HH24:MI:SS.FF6')";
 | 
			
		||||
                            case "System.Decimal": return $"cast({getExp(operandExp)} as number)";
 | 
			
		||||
                            case "System.Double": return $"cast({getExp(operandExp)} as number)";
 | 
			
		||||
                            case "System.Int16":
 | 
			
		||||
                            case "System.Int32":
 | 
			
		||||
                            case "System.Int64":
 | 
			
		||||
                            case "System.SByte": return $"cast({getExp(operandExp)} as number)";
 | 
			
		||||
                            case "System.Single": return $"cast({getExp(operandExp)} as number)";
 | 
			
		||||
                            case "System.String": return $"to_char({getExp(operandExp)})";
 | 
			
		||||
                            case "System.UInt16":
 | 
			
		||||
                            case "System.UInt32":
 | 
			
		||||
                            case "System.UInt64": return $"cast({getExp(operandExp)} as number)";
 | 
			
		||||
                            case "System.Guid": return $"substr(to_char({getExp(operandExp)}), 1, 36)";
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
                case ExpressionType.Call:
 | 
			
		||||
                    var callExp = exp as MethodCallExpression;
 | 
			
		||||
 | 
			
		||||
                    switch (callExp.Method.Name)
 | 
			
		||||
                    {
 | 
			
		||||
                        case "Parse":
 | 
			
		||||
                        case "TryParse":
 | 
			
		||||
                            switch (callExp.Method.DeclaringType.NullableTypeOrThis().ToString())
 | 
			
		||||
                            {
 | 
			
		||||
                                //case "System.Boolean": return $"({getExp(callExp.Arguments[0])} not in ('0','false'))";
 | 
			
		||||
                                case "System.Byte": return $"cast({getExp(callExp.Arguments[0])} as number)";
 | 
			
		||||
                                case "System.Char": return $"substr(to_char({getExp(callExp.Arguments[0])}), 1, 1)";
 | 
			
		||||
                                case "System.DateTime": return $"to_timestamp({getExp(callExp.Arguments[0])},'YYYY-MM-DD HH24:MI:SS.FF6')";
 | 
			
		||||
                                case "System.Decimal": return $"cast({getExp(callExp.Arguments[0])} as number)";
 | 
			
		||||
                                case "System.Double": return $"cast({getExp(callExp.Arguments[0])} as number)";
 | 
			
		||||
                                case "System.Int16":
 | 
			
		||||
                                case "System.Int32":
 | 
			
		||||
                                case "System.Int64":
 | 
			
		||||
                                case "System.SByte": return $"cast({getExp(callExp.Arguments[0])} as number)";
 | 
			
		||||
                                case "System.Single": return $"cast({getExp(callExp.Arguments[0])} as number)";
 | 
			
		||||
                                case "System.UInt16":
 | 
			
		||||
                                case "System.UInt32":
 | 
			
		||||
                                case "System.UInt64": return $"cast({getExp(callExp.Arguments[0])} as number)";
 | 
			
		||||
                                case "System.Guid": return $"substr(to_char({getExp(callExp.Arguments[0])}), 1, 36)";
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "NewGuid":
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "Next":
 | 
			
		||||
                            if (callExp.Object?.Type == typeof(Random)) return "cast(dbms_random.value*1000000000 as smallint)";
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "NextDouble":
 | 
			
		||||
                            if (callExp.Object?.Type == typeof(Random)) return "dbms_random.value";
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "Random":
 | 
			
		||||
                            if (callExp.Method.DeclaringType.IsNumberType()) return "dbms_random.value";
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "ToString":
 | 
			
		||||
                            if (callExp.Object != null) return callExp.Arguments.Count == 0 ? $"to_char({getExp(callExp.Object)})" : null;
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var objExp = callExp.Object;
 | 
			
		||||
                    var objType = objExp?.Type;
 | 
			
		||||
                    if (objType?.FullName == "System.Byte[]") return null;
 | 
			
		||||
 | 
			
		||||
                    var argIndex = 0;
 | 
			
		||||
                    if (objType == null && callExp.Method.DeclaringType == typeof(Enumerable))
 | 
			
		||||
                    {
 | 
			
		||||
                        objExp = callExp.Arguments.FirstOrDefault();
 | 
			
		||||
                        objType = objExp?.Type;
 | 
			
		||||
                        argIndex++;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (objType == null) objType = callExp.Method.DeclaringType;
 | 
			
		||||
                    if (objType != null || objType.IsArray || typeof(IList).IsAssignableFrom(callExp.Method.DeclaringType))
 | 
			
		||||
                    {
 | 
			
		||||
                        var left = objExp == null ? null : getExp(objExp);
 | 
			
		||||
                        switch (callExp.Method.Name)
 | 
			
		||||
                        {
 | 
			
		||||
                            case "Contains":
 | 
			
		||||
                                //判断 in
 | 
			
		||||
                                return $"({getExp(callExp.Arguments[argIndex])}) in {left}";
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
                case ExpressionType.NewArrayInit:
 | 
			
		||||
                    var arrExp = exp as NewArrayExpression;
 | 
			
		||||
                    var arrSb = new StringBuilder();
 | 
			
		||||
                    arrSb.Append("(");
 | 
			
		||||
                    for (var a = 0; a < arrExp.Expressions.Count; a++)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (a > 0) arrSb.Append(",");
 | 
			
		||||
                        arrSb.Append(getExp(arrExp.Expressions[a]));
 | 
			
		||||
                    }
 | 
			
		||||
                    if (arrSb.Length == 1) arrSb.Append("NULL");
 | 
			
		||||
                    return arrSb.Append(")").ToString();
 | 
			
		||||
                case ExpressionType.ListInit:
 | 
			
		||||
                    var listExp = exp as ListInitExpression;
 | 
			
		||||
                    var listSb = new StringBuilder();
 | 
			
		||||
                    listSb.Append("(");
 | 
			
		||||
                    for (var a = 0; a < listExp.Initializers.Count; a++)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (listExp.Initializers[a].Arguments.Any() == false) continue;
 | 
			
		||||
                        if (a > 0) listSb.Append(",");
 | 
			
		||||
                        listSb.Append(getExp(listExp.Initializers[a].Arguments.FirstOrDefault()));
 | 
			
		||||
                    }
 | 
			
		||||
                    if (listSb.Length == 1) listSb.Append("NULL");
 | 
			
		||||
                    return listSb.Append(")").ToString();
 | 
			
		||||
                case ExpressionType.New:
 | 
			
		||||
                    var newExp = exp as NewExpression;
 | 
			
		||||
                    if (typeof(IList).IsAssignableFrom(newExp.Type))
 | 
			
		||||
                    {
 | 
			
		||||
                        if (newExp.Arguments.Count == 0) return "(NULL)";
 | 
			
		||||
                        if (typeof(IEnumerable).IsAssignableFrom(newExp.Arguments[0].Type) == false) return "(NULL)";
 | 
			
		||||
                        return getExp(newExp.Arguments[0]);
 | 
			
		||||
                    }
 | 
			
		||||
                    return null;
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override string ExpressionLambdaToSqlMemberAccessString(MemberExpression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            if (exp.Expression == null)
 | 
			
		||||
            {
 | 
			
		||||
                switch (exp.Member.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "Empty": return "''";
 | 
			
		||||
                }
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            var left = ExpressionLambdaToSql(exp.Expression, tsc);
 | 
			
		||||
            switch (exp.Member.Name)
 | 
			
		||||
            {
 | 
			
		||||
                case "Length": return $"length({left})";
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        public override string ExpressionLambdaToSqlMemberAccessDateTime(MemberExpression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            if (exp.Expression == null)
 | 
			
		||||
            {
 | 
			
		||||
                switch (exp.Member.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "Now": return "systimestamp";
 | 
			
		||||
                    case "UtcNow": return "sys_extract_utc(systimestamp)";
 | 
			
		||||
                    case "Today": return "trunc(systimestamp)";
 | 
			
		||||
                    case "MinValue": return "to_timestamp('0001-01-01 00:00:00','YYYY-MM-DD HH24:MI:SS.FF6')";
 | 
			
		||||
                    case "MaxValue": return "to_timestamp('9999-12-31 23:59:59','YYYY-MM-DD HH24:MI:SS.FF6')";
 | 
			
		||||
                }
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            var left = ExpressionLambdaToSql(exp.Expression, tsc);
 | 
			
		||||
            switch (exp.Member.Name)
 | 
			
		||||
            {
 | 
			
		||||
                case "Date": return $"trunc({left})";
 | 
			
		||||
                case "TimeOfDay": return $"({left}-trunc({left}))";
 | 
			
		||||
                case "DayOfWeek": return $"case when to_char({left})='7' then 0 else cast(to_char({left}) as number) end";
 | 
			
		||||
                case "Day": return $"cast(to_char({left},'DD') as number)";
 | 
			
		||||
                case "DayOfYear": return $"cast(to_char({left},'DDD') as number)";
 | 
			
		||||
                case "Month": return $"cast(to_char({left},'MM') as number)";
 | 
			
		||||
                case "Year": return $"cast(to_char({left},'YYYY') as number)";
 | 
			
		||||
                case "Hour": return $"cast(to_char({left},'HH24') as number)";
 | 
			
		||||
                case "Minute": return $"cast(to_char({left},'MI') as number)";
 | 
			
		||||
                case "Second": return $"cast(to_char({left},'SS') as number)";
 | 
			
		||||
                case "Millisecond": return $"cast(to_char({left},'FF3') as number)";
 | 
			
		||||
                case "Ticks": return $"cast(to_char({left},'FF7') as number)";
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        public override string ExpressionLambdaToSqlMemberAccessTimeSpan(MemberExpression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            if (exp.Expression == null)
 | 
			
		||||
            {
 | 
			
		||||
                switch (exp.Member.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "Zero": return "numtodsinterval(0,'second')";
 | 
			
		||||
                    case "MinValue": return "numtodsinterval(-233720368.5477580,'second')";
 | 
			
		||||
                    case "MaxValue": return "numtodsinterval(233720368.5477580,'second')";
 | 
			
		||||
                }
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            var left = ExpressionLambdaToSql(exp.Expression, tsc);
 | 
			
		||||
            switch (exp.Member.Name)
 | 
			
		||||
            {
 | 
			
		||||
                case "Days": return $"extract(day from {left})";
 | 
			
		||||
                case "Hours": return $"extract(hour from {left})";
 | 
			
		||||
                case "Milliseconds": return $"cast(substr(extract(second from {left})-floor(extract(second from {left})),2,3) as number)";
 | 
			
		||||
                case "Minutes": return $"extract(minute from {left})";
 | 
			
		||||
                case "Seconds": return $"floor(extract(second from {left}))";
 | 
			
		||||
                case "Ticks": return $"(extract(day from {left})*86400+extract(hour from {left})*3600+extract(minute from {left})*60+extract(second from {left}))*10000000";
 | 
			
		||||
                case "TotalDays": return $"extract(day from {left})";
 | 
			
		||||
                case "TotalHours": return $"(extract(day from {left})*24+extract(hour from {left}))";
 | 
			
		||||
                case "TotalMilliseconds": return $"(extract(day from {left})*86400+extract(hour from {left})*3600+extract(minute from {left})*60+extract(second from {left}))*1000";
 | 
			
		||||
                case "TotalMinutes": return $"(extract(day from {left})*1440+extract(hour from {left})*60+extract(minute from {left}))";
 | 
			
		||||
                case "TotalSeconds": return $"(extract(day from {left})*86400+extract(hour from {left})*3600+extract(minute from {left})*60+extract(second from {left}))";
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override string ExpressionLambdaToSqlCallString(MethodCallExpression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            Func<Expression, string> getExp = exparg => ExpressionLambdaToSql(exparg, tsc);
 | 
			
		||||
            if (exp.Object == null)
 | 
			
		||||
            {
 | 
			
		||||
                switch (exp.Method.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "IsNullOrEmpty":
 | 
			
		||||
                        var arg1 = getExp(exp.Arguments[0]);
 | 
			
		||||
                        return $"({arg1} is null or {arg1} = '')";
 | 
			
		||||
                    case "IsNullOrWhiteSpace":
 | 
			
		||||
                        var arg2 = getExp(exp.Arguments[0]);
 | 
			
		||||
                        return $"({arg2} is null or {arg2} = '' or ltrim({arg2}) = '')";
 | 
			
		||||
                    case "Concat":
 | 
			
		||||
                        return _common.StringConcat(exp.Arguments.Select(a => getExp(a)).ToArray(), null);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                var left = getExp(exp.Object);
 | 
			
		||||
                switch (exp.Method.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "StartsWith":
 | 
			
		||||
                    case "EndsWith":
 | 
			
		||||
                    case "Contains":
 | 
			
		||||
                        var args0Value = getExp(exp.Arguments[0]);
 | 
			
		||||
                        if (args0Value == "NULL") return $"({left}) IS NULL";
 | 
			
		||||
                        if (exp.Method.Name == "StartsWith") return $"({left}) LIKE {(args0Value.EndsWith("'") ? args0Value.Insert(args0Value.Length - 1, "%") : $"(to_char({args0Value})||'%')")}";
 | 
			
		||||
                        if (exp.Method.Name == "EndsWith") return $"({left}) LIKE {(args0Value.StartsWith("'") ? args0Value.Insert(1, "%") : $"('%'||to_char({args0Value}))")}";
 | 
			
		||||
                        if (args0Value.StartsWith("'") && args0Value.EndsWith("'")) return $"({left}) LIKE {args0Value.Insert(1, "%").Insert(args0Value.Length, "%")}";
 | 
			
		||||
                        return $"({left}) LIKE ('%'||to_char({args0Value})||'%')";
 | 
			
		||||
                    case "ToLower": return $"lower({left})";
 | 
			
		||||
                    case "ToUpper": return $"upper({left})";
 | 
			
		||||
                    case "Substring":
 | 
			
		||||
                        var substrArgs1 = getExp(exp.Arguments[0]);
 | 
			
		||||
                        if (long.TryParse(substrArgs1, out var testtrylng1)) substrArgs1 = (testtrylng1 + 1).ToString();
 | 
			
		||||
                        else substrArgs1 += "+1";
 | 
			
		||||
                        if (exp.Arguments.Count == 1) return $"substr({left}, {substrArgs1})";
 | 
			
		||||
                        return $"substr({left}, {substrArgs1}, {getExp(exp.Arguments[1])})";
 | 
			
		||||
                    case "IndexOf":
 | 
			
		||||
                        var indexOfFindStr = getExp(exp.Arguments[0]);
 | 
			
		||||
                        if (exp.Arguments.Count > 1 && exp.Arguments[1].Type.FullName == "System.Int32")
 | 
			
		||||
                        {
 | 
			
		||||
                            var locateArgs1 = getExp(exp.Arguments[1]);
 | 
			
		||||
                            if (long.TryParse(locateArgs1, out var testtrylng2)) locateArgs1 = (testtrylng2 + 1).ToString();
 | 
			
		||||
                            else locateArgs1 += "+1";
 | 
			
		||||
                            return $"(instr({left}, {indexOfFindStr}, {locateArgs1}, 1)-1)";
 | 
			
		||||
                        }
 | 
			
		||||
                        return $"(instr({left}, {indexOfFindStr}, 1, 1))-1";
 | 
			
		||||
                    case "PadLeft":
 | 
			
		||||
                        if (exp.Arguments.Count == 1) return $"lpad({left}, {getExp(exp.Arguments[0])}, ' ')";
 | 
			
		||||
                        return $"lpad({left}, {getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})";
 | 
			
		||||
                    case "PadRight":
 | 
			
		||||
                        if (exp.Arguments.Count == 1) return $"rpad({left}, {getExp(exp.Arguments[0])}, ' ')";
 | 
			
		||||
                        return $"rpad({left}, {getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})";
 | 
			
		||||
                    case "Trim":
 | 
			
		||||
                    case "TrimStart":
 | 
			
		||||
                    case "TrimEnd":
 | 
			
		||||
                        if (exp.Arguments.Count == 0)
 | 
			
		||||
                        {
 | 
			
		||||
                            if (exp.Method.Name == "Trim") return $"trim({left})";
 | 
			
		||||
                            if (exp.Method.Name == "TrimStart") return $"ltrim({left})";
 | 
			
		||||
                            if (exp.Method.Name == "TrimEnd") return $"rtrim({left})";
 | 
			
		||||
                        }
 | 
			
		||||
                        foreach (var argsTrim02 in exp.Arguments)
 | 
			
		||||
                        {
 | 
			
		||||
                            var argsTrim01s = new[] { argsTrim02 };
 | 
			
		||||
                            if (argsTrim02.NodeType == ExpressionType.NewArrayInit)
 | 
			
		||||
                            {
 | 
			
		||||
                                var arritem = argsTrim02 as NewArrayExpression;
 | 
			
		||||
                                argsTrim01s = arritem.Expressions.ToArray();
 | 
			
		||||
                            }
 | 
			
		||||
                            foreach (var argsTrim01 in argsTrim01s)
 | 
			
		||||
                            {
 | 
			
		||||
                                if (exp.Method.Name == "Trim") left = $"trim(both {getExp(argsTrim01)} from {left})";
 | 
			
		||||
                                if (exp.Method.Name == "TrimStart") left = $"ltrim({left},{getExp(argsTrim01)})";
 | 
			
		||||
                                if (exp.Method.Name == "TrimEnd") left = $"rtrim({left},{getExp(argsTrim01)})";
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        return left;
 | 
			
		||||
                    case "Replace": return $"replace({left}, {getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})";
 | 
			
		||||
                    case "CompareTo": return $"case when {left} = {getExp(exp.Arguments[0])} then 0 when {left} > {getExp(exp.Arguments[0])} then 1 else -1 end";
 | 
			
		||||
                    case "Equals": return $"({left} = {getExp(exp.Arguments[0])})";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        public override string ExpressionLambdaToSqlCallMath(MethodCallExpression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            Func<Expression, string> getExp = exparg => ExpressionLambdaToSql(exparg, tsc);
 | 
			
		||||
            switch (exp.Method.Name)
 | 
			
		||||
            {
 | 
			
		||||
                case "Abs": return $"abs({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Sign": return $"sign({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Floor": return $"floor({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Ceiling": return $"ceil({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Round":
 | 
			
		||||
                    if (exp.Arguments.Count > 1 && exp.Arguments[1].Type.FullName == "System.Int32") return $"round({getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})";
 | 
			
		||||
                    return $"round({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Exp": return $"exp({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Log":
 | 
			
		||||
                    if (exp.Arguments.Count > 1) return $"log({getExp(exp.Arguments[1])},{getExp(exp.Arguments[0])})";
 | 
			
		||||
                    return $"log(2.7182818284590451,{getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Log10": return $"log(10,{getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Pow": return $"power({getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})";
 | 
			
		||||
                case "Sqrt": return $"sqrt({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Cos": return $"cos({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Sin": return $"sin({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Tan": return $"tan({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Acos": return $"acos({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Asin": return $"asin({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Atan": return $"atan({getExp(exp.Arguments[0])})";
 | 
			
		||||
                //case "Atan2": return $"atan2({getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})";
 | 
			
		||||
                case "Truncate": return $"trunc({getExp(exp.Arguments[0])}, 0)";
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        public override string ExpressionLambdaToSqlCallDateTime(MethodCallExpression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            Func<Expression, string> getExp = exparg => ExpressionLambdaToSql(exparg, tsc);
 | 
			
		||||
            if (exp.Object == null)
 | 
			
		||||
            {
 | 
			
		||||
                switch (exp.Method.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "Compare": return $"extract(day from ({getExp(exp.Arguments[0])}-({getExp(exp.Arguments[1])})))";
 | 
			
		||||
                    case "DaysInMonth": return $"cast(to_char(last_day(({getExp(exp.Arguments[0])})||'-'||({getExp(exp.Arguments[1])})||'-01'),'DD') as number)";
 | 
			
		||||
                    case "Equals": return $"({getExp(exp.Arguments[0])} = {getExp(exp.Arguments[1])})";
 | 
			
		||||
 | 
			
		||||
                    case "IsLeapYear":
 | 
			
		||||
                        var isLeapYearArgs1 = getExp(exp.Arguments[0]);
 | 
			
		||||
                        return $"(mod({isLeapYearArgs1},4)=0 AND mod({isLeapYearArgs1},100)<>0 OR mod({isLeapYearArgs1},400)=0)";
 | 
			
		||||
 | 
			
		||||
                    case "Parse": return $"to_timestamp({getExp(exp.Arguments[0])},'YYYY-MM-DD HH24:MI:SS.FF6')";
 | 
			
		||||
                    case "ParseExact":
 | 
			
		||||
                    case "TryParse":
 | 
			
		||||
                    case "TryParseExact": return $"to_timestamp({getExp(exp.Arguments[0])},'YYYY-MM-DD HH24:MI:SS.FF6')";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                var left = getExp(exp.Object);
 | 
			
		||||
                var args1 = exp.Arguments.Count == 0 ? null : getExp(exp.Arguments[0]);
 | 
			
		||||
                switch (exp.Method.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "Add": return $"({left}+{args1})";
 | 
			
		||||
                    case "AddDays": return $"({left}+{args1})";
 | 
			
		||||
                    case "AddHours": return $"({left}+({args1})/24)";
 | 
			
		||||
                    case "AddMilliseconds": return $"({left}+({args1})/86400000)";
 | 
			
		||||
                    case "AddMinutes": return $"({left}+({args1})/1440)";
 | 
			
		||||
                    case "AddMonths": return $"add_months({left},{args1})";
 | 
			
		||||
                    case "AddSeconds": return $"({left}+({args1})/86400)";
 | 
			
		||||
                    case "AddTicks": return $"({left}+({args1})/864000000000)";
 | 
			
		||||
                    case "AddYears": return $"add_months({left},({args1})*12)";
 | 
			
		||||
                    case "Subtract":
 | 
			
		||||
                        switch ((exp.Arguments[0].Type.IsNullableType() ? exp.Arguments[0].Type.GenericTypeArguments.FirstOrDefault() : exp.Arguments[0].Type).FullName)
 | 
			
		||||
                        {
 | 
			
		||||
                            case "System.DateTime": return $"numtodsinterval(({left}+0)-({args1}+0),'day')";
 | 
			
		||||
                            case "System.TimeSpan": return $"({left}-{args1})";
 | 
			
		||||
                        }
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "Equals": return $"({left} = {getExp(exp.Arguments[0])})";
 | 
			
		||||
                    case "CompareTo": return $"extract(day from ({left}-({getExp(exp.Arguments[0])})))";
 | 
			
		||||
                    case "ToString": return exp.Arguments.Count == 0 ? $"to_char({left},'YYYY-MM-DD HH24:MI:SS.FF6')" : null;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        public override string ExpressionLambdaToSqlCallTimeSpan(MethodCallExpression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            Func<Expression, string> getExp = exparg => ExpressionLambdaToSql(exparg, tsc);
 | 
			
		||||
            if (exp.Object == null)
 | 
			
		||||
            {
 | 
			
		||||
                switch (exp.Method.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "Compare": return $"extract(day from ({getExp(exp.Arguments[0])}-({getExp(exp.Arguments[1])})))";
 | 
			
		||||
                    case "Equals": return $"({getExp(exp.Arguments[0])} = {getExp(exp.Arguments[1])})";
 | 
			
		||||
                    case "FromDays": return $"numtodsinterval(({getExp(exp.Arguments[0])})*{(long)60 * 60 * 24},'second')";
 | 
			
		||||
                    case "FromHours": return $"numtodsinterval(({getExp(exp.Arguments[0])})*{(long)60 * 60},'second')";
 | 
			
		||||
                    case "FromMilliseconds": return $"numtodsinterval(({getExp(exp.Arguments[0])})/1000,'second')";
 | 
			
		||||
                    case "FromMinutes": return $"numtodsinterval(({getExp(exp.Arguments[0])})*60,'second')";
 | 
			
		||||
                    case "FromSeconds": return $"numtodsinterval(({getExp(exp.Arguments[0])}),'second')";
 | 
			
		||||
                    case "FromTicks": return $"numtodsinterval(({getExp(exp.Arguments[0])})/10000000,'second')";
 | 
			
		||||
                    case "Parse": return $"cast({getExp(exp.Arguments[0])} as interval day(9) to second(7))";
 | 
			
		||||
                    case "ParseExact":
 | 
			
		||||
                    case "TryParse":
 | 
			
		||||
                    case "TryParseExact": return $"cast({getExp(exp.Arguments[0])} as interval day(9) to second(7))";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                var left = getExp(exp.Object);
 | 
			
		||||
                var args1 = exp.Arguments.Count == 0 ? null : getExp(exp.Arguments[0]);
 | 
			
		||||
                switch (exp.Method.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "Add": return $"({left}+{args1})";
 | 
			
		||||
                    case "Subtract": return $"({left}-({args1}))";
 | 
			
		||||
                    case "Equals": return $"({left} = {getExp(exp.Arguments[0])})";
 | 
			
		||||
                    case "CompareTo": return $"extract(day from ({left}-({getExp(exp.Arguments[0])})))";
 | 
			
		||||
                    case "ToString": return $"to_char({left})";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        public override string ExpressionLambdaToSqlCallConvert(MethodCallExpression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            Func<Expression, string> getExp = exparg => ExpressionLambdaToSql(exparg, tsc);
 | 
			
		||||
            if (exp.Object == null)
 | 
			
		||||
            {
 | 
			
		||||
                switch (exp.Method.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    //case "ToBoolean": return $"({getExp(exp.Arguments[0])} not in ('0','false'))";
 | 
			
		||||
                    case "ToByte": return $"cast({getExp(exp.Arguments[0])} as number)";
 | 
			
		||||
                    case "ToChar": return $"substr(to_char({getExp(exp.Arguments[0])}), 1, 1)";
 | 
			
		||||
                    case "ToDateTime": return $"to_timestamp({getExp(exp.Arguments[0])},'YYYY-MM-DD HH24:MI:SS.FF6')";
 | 
			
		||||
                    case "ToDecimal": return $"cast({getExp(exp.Arguments[0])} as number)";
 | 
			
		||||
                    case "ToDouble": return $"cast({getExp(exp.Arguments[0])} as number)";
 | 
			
		||||
                    case "ToInt16":
 | 
			
		||||
                    case "ToInt32":
 | 
			
		||||
                    case "ToInt64":
 | 
			
		||||
                    case "ToSByte": return $"cast({getExp(exp.Arguments[0])} as number)";
 | 
			
		||||
                    case "ToSingle": return $"cast({getExp(exp.Arguments[0])} as number)";
 | 
			
		||||
                    case "ToString": return $"to_char({getExp(exp.Arguments[0])})";
 | 
			
		||||
                    case "ToUInt16":
 | 
			
		||||
                    case "ToUInt32":
 | 
			
		||||
                    case "ToUInt64": return $"cast({getExp(exp.Arguments[0])} as number)";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										64
									
								
								Providers/FreeSql.Provider.Odbc/Oracle/OdbcOracleProvider.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								Providers/FreeSql.Provider.Odbc/Oracle/OdbcOracleProvider.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using FreeSql.Internal.CommonProvider;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data.Common;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.Oracle
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    public class OdbcOracleProvider<TMark> : IFreeSql<TMark>
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        public ISelect<T1> Select<T1>() where T1 : class => new OdbcOracleSelect<T1>(this, this.InternalCommonUtils, this.InternalCommonExpression, null);
 | 
			
		||||
        public ISelect<T1> Select<T1>(object dywhere) where T1 : class => new OdbcOracleSelect<T1>(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere);
 | 
			
		||||
        public IInsert<T1> Insert<T1>() where T1 : class => new OdbcOracleInsert<T1>(this, this.InternalCommonUtils, this.InternalCommonExpression);
 | 
			
		||||
        public IInsert<T1> Insert<T1>(T1 source) where T1 : class => this.Insert<T1>().AppendData(source);
 | 
			
		||||
        public IInsert<T1> Insert<T1>(T1[] source) where T1 : class => this.Insert<T1>().AppendData(source);
 | 
			
		||||
        public IInsert<T1> Insert<T1>(IEnumerable<T1> source) where T1 : class => this.Insert<T1>().AppendData(source);
 | 
			
		||||
        public IUpdate<T1> Update<T1>() where T1 : class => new OdbcOracleUpdate<T1>(this, this.InternalCommonUtils, this.InternalCommonExpression, null);
 | 
			
		||||
        public IUpdate<T1> Update<T1>(object dywhere) where T1 : class => new OdbcOracleUpdate<T1>(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere);
 | 
			
		||||
        public IDelete<T1> Delete<T1>() where T1 : class => new OdbcOracleDelete<T1>(this, this.InternalCommonUtils, this.InternalCommonExpression, null);
 | 
			
		||||
        public IDelete<T1> Delete<T1>(object dywhere) where T1 : class => new OdbcOracleDelete<T1>(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere);
 | 
			
		||||
 | 
			
		||||
        public IAdo Ado { get; }
 | 
			
		||||
        public IAop Aop { get; }
 | 
			
		||||
        public ICodeFirst CodeFirst { get; }
 | 
			
		||||
        public IDbFirst DbFirst { get; }
 | 
			
		||||
        public OdbcOracleProvider(string masterConnectionString, string[] slaveConnectionString)
 | 
			
		||||
        {
 | 
			
		||||
            this.InternalCommonUtils = new OdbcOracleUtils(this);
 | 
			
		||||
            this.InternalCommonExpression = new OdbcOracleExpression(this.InternalCommonUtils);
 | 
			
		||||
 | 
			
		||||
            this.Ado = new OdbcOracleAdo(this.InternalCommonUtils, masterConnectionString, slaveConnectionString);
 | 
			
		||||
            this.Aop = new AopProvider();
 | 
			
		||||
 | 
			
		||||
            this.DbFirst = new OdbcOracleDbFirst(this, this.InternalCommonUtils, this.InternalCommonExpression);
 | 
			
		||||
            this.CodeFirst = new OdbcOracleCodeFirst(this, this.InternalCommonUtils, this.InternalCommonExpression);
 | 
			
		||||
 | 
			
		||||
            //this.Aop.AuditValue += new EventHandler<Aop.AuditValueEventArgs>((_, e) =>
 | 
			
		||||
            //{
 | 
			
		||||
            //    if (e.Value == null && e.Column.Attribute.IsPrimary == false && e.Column.Attribute.IsIdentity == false)
 | 
			
		||||
            //        e.Value = Utils.GetDataReaderValue(e.Property.PropertyType.NullableTypeOrThis(), e.Column.Attribute.DbDefautValue);
 | 
			
		||||
            //});
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        internal CommonUtils InternalCommonUtils { get; }
 | 
			
		||||
        internal CommonExpression InternalCommonExpression { get; }
 | 
			
		||||
 | 
			
		||||
        public void Transaction(Action handler) => Ado.Transaction(handler);
 | 
			
		||||
 | 
			
		||||
        public void Transaction(Action handler, TimeSpan timeout) => Ado.Transaction(handler, timeout);
 | 
			
		||||
 | 
			
		||||
        ~OdbcOracleProvider()
 | 
			
		||||
        {
 | 
			
		||||
            this.Dispose();
 | 
			
		||||
        }
 | 
			
		||||
        bool _isdisposed = false;
 | 
			
		||||
        public void Dispose()
 | 
			
		||||
        {
 | 
			
		||||
            if (_isdisposed) return;
 | 
			
		||||
            (this.Ado as AdoProvider)?.Dispose();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										111
									
								
								Providers/FreeSql.Provider.Odbc/Oracle/OdbcOracleUtils.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								Providers/FreeSql.Provider.Odbc/Oracle/OdbcOracleUtils.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,111 @@
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using FreeSql.Internal.Model;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data.Common;
 | 
			
		||||
using System.Data.Odbc;
 | 
			
		||||
using System.Linq.Expressions;
 | 
			
		||||
using System.Text;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.Oracle
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    class OdbcOracleUtils : CommonUtils
 | 
			
		||||
    {
 | 
			
		||||
        public OdbcOracleUtils(IFreeSql orm) : base(orm)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override DbParameter AppendParamter(List<DbParameter> _params, string parameterName, Type type, object value)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrEmpty(parameterName)) parameterName = $"p_{_params?.Count}";
 | 
			
		||||
            var dbtype = (OdbcType)_orm.CodeFirst.GetDbInfo(type)?.type;
 | 
			
		||||
            switch (dbtype)
 | 
			
		||||
            {
 | 
			
		||||
                case OdbcType.Bit:
 | 
			
		||||
                    if (value == null) value = null;
 | 
			
		||||
                    else value = (bool)value == true ? 1 : 0;
 | 
			
		||||
                    dbtype = OdbcType.Int;
 | 
			
		||||
                    break;
 | 
			
		||||
               
 | 
			
		||||
                case OdbcType.Char:
 | 
			
		||||
                case OdbcType.NChar:
 | 
			
		||||
                case OdbcType.VarChar:
 | 
			
		||||
                case OdbcType.NVarChar:
 | 
			
		||||
                case OdbcType.Text:
 | 
			
		||||
                case OdbcType.NText:
 | 
			
		||||
                    value = string.Concat(value);
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
            var ret = new OdbcParameter { ParameterName = QuoteParamterName(parameterName), OdbcType = dbtype, Value = value };
 | 
			
		||||
            _params?.Add(ret);
 | 
			
		||||
            return ret;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override DbParameter[] GetDbParamtersByObject(string sql, object obj) =>
 | 
			
		||||
            Utils.GetDbParamtersByObject<OdbcParameter>(sql, obj, null, (name, type, value) =>
 | 
			
		||||
            {
 | 
			
		||||
                var dbtype = (OdbcType)_orm.CodeFirst.GetDbInfo(type)?.type;
 | 
			
		||||
                switch (dbtype)
 | 
			
		||||
                {
 | 
			
		||||
                    case OdbcType.Bit:
 | 
			
		||||
                        if (value == null) value = null;
 | 
			
		||||
                        else value = (bool)value == true ? 1 : 0;
 | 
			
		||||
                        dbtype = OdbcType.Int;
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case OdbcType.Char:
 | 
			
		||||
                    case OdbcType.NChar:
 | 
			
		||||
                    case OdbcType.VarChar:
 | 
			
		||||
                    case OdbcType.NVarChar:
 | 
			
		||||
                    case OdbcType.Text:
 | 
			
		||||
                    case OdbcType.NText:
 | 
			
		||||
                        value = string.Concat(value);
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
                var ret = new OdbcParameter { ParameterName = $":{name}", OdbcType = dbtype, Value = value };
 | 
			
		||||
                return ret;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        public override string FormatSql(string sql, params object[] args) => sql?.FormatOdbcOracle(args);
 | 
			
		||||
        public override string QuoteSqlName(string name)
 | 
			
		||||
        {
 | 
			
		||||
            var nametrim = name.Trim();
 | 
			
		||||
            if (nametrim.StartsWith("(") && nametrim.EndsWith(")"))
 | 
			
		||||
                return nametrim; //原生SQL
 | 
			
		||||
            return $"\"{nametrim.Trim('"').Replace(".", "\".\"")}\"";
 | 
			
		||||
        }
 | 
			
		||||
        public override string TrimQuoteSqlName(string name)
 | 
			
		||||
        {
 | 
			
		||||
            var nametrim = name.Trim();
 | 
			
		||||
            if (nametrim.StartsWith("(") && nametrim.EndsWith(")"))
 | 
			
		||||
                return nametrim; //原生SQL
 | 
			
		||||
            return $"{nametrim.Trim('"').Replace("\".\"", ".").Replace(".\"", ".")}";
 | 
			
		||||
        }
 | 
			
		||||
        public override string QuoteParamterName(string name) => $":{(_orm.CodeFirst.IsSyncStructureToLower ? name.ToLower() : name)}";
 | 
			
		||||
        public override string IsNull(string sql, object value) => $"nvl({sql}, {value})";
 | 
			
		||||
        public override string StringConcat(string[] objs, Type[] types) => $"{string.Join(" || ", objs)}";
 | 
			
		||||
        public override string Mod(string left, string right, Type leftType, Type rightType) => $"mod({left}, {right})";
 | 
			
		||||
        public override string Div(string left, string right, Type leftType, Type rightType) => $"trunc({left} / {right})";
 | 
			
		||||
 | 
			
		||||
        public override string QuoteWriteParamter(Type type, string paramterName) => paramterName;
 | 
			
		||||
        public override string QuoteReadColumn(Type type, string columnName) => columnName;
 | 
			
		||||
 | 
			
		||||
        public override string GetNoneParamaterSqlValue(List<DbParameter> specialParams, Type type, object value)
 | 
			
		||||
        {
 | 
			
		||||
            if (value == null) return "NULL";
 | 
			
		||||
            if (type == typeof(byte[]))
 | 
			
		||||
            {
 | 
			
		||||
                var bytes = value as byte[];
 | 
			
		||||
                var sb = new StringBuilder().Append("rawtohex('0x");
 | 
			
		||||
                foreach (var vc in bytes)
 | 
			
		||||
                {
 | 
			
		||||
                    if (vc < 10) sb.Append("0");
 | 
			
		||||
                    sb.Append(vc.ToString("X"));
 | 
			
		||||
                }
 | 
			
		||||
                return sb.Append("')").ToString();
 | 
			
		||||
            }
 | 
			
		||||
            return FormatSql("{0}", value, 1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,105 @@
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.SqlServer
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    class OdbcSqlServerDelete<T1> : Internal.CommonProvider.DeleteProvider<T1> where T1 : class
 | 
			
		||||
    {
 | 
			
		||||
        public OdbcSqlServerDelete(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere)
 | 
			
		||||
            : base(orm, commonUtils, commonExpression, dywhere)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override List<T1> ExecuteDeleted()
 | 
			
		||||
        {
 | 
			
		||||
            var sql = this.ToSql();
 | 
			
		||||
            if (string.IsNullOrEmpty(sql)) return new List<T1>();
 | 
			
		||||
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            sb.Append(" OUTPUT ");
 | 
			
		||||
            var colidx = 0;
 | 
			
		||||
            foreach (var col in _table.Columns.Values)
 | 
			
		||||
            {
 | 
			
		||||
                if (colidx > 0) sb.Append(", ");
 | 
			
		||||
                sb.Append("DELETED.").Append(_commonUtils.QuoteReadColumn(col.Attribute.MapType, _commonUtils.QuoteSqlName(col.Attribute.Name))).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName));
 | 
			
		||||
                ++colidx;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var validx = sql.IndexOf(" WHERE ");
 | 
			
		||||
            if (validx == -1) throw new ArgumentException("找不到 WHERE ");
 | 
			
		||||
            sb.Insert(0, sql.Substring(0, validx));
 | 
			
		||||
            sb.Append(sql.Substring(validx));
 | 
			
		||||
 | 
			
		||||
            sql = sb.ToString();
 | 
			
		||||
            var dbParms = _params.ToArray();
 | 
			
		||||
            var before = new Aop.CurdBeforeEventArgs(_table.Type, Aop.CurdType.Delete, sql, dbParms);
 | 
			
		||||
            _orm.Aop.CurdBefore?.Invoke(this, before);
 | 
			
		||||
            var ret = new List<T1>();
 | 
			
		||||
            Exception exception = null;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                ret = _orm.Ado.Query<T1>(_connection, _transaction, CommandType.Text, sql, dbParms);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                exception = ex;
 | 
			
		||||
                throw ex;
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                var after = new Aop.CurdAfterEventArgs(before, exception, ret);
 | 
			
		||||
                _orm.Aop.CurdAfter?.Invoke(this, after);
 | 
			
		||||
            }
 | 
			
		||||
            this.ClearData();
 | 
			
		||||
            return ret;
 | 
			
		||||
        }
 | 
			
		||||
        async public override Task<List<T1>> ExecuteDeletedAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var sql = this.ToSql();
 | 
			
		||||
            if (string.IsNullOrEmpty(sql)) return new List<T1>();
 | 
			
		||||
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            sb.Append(" OUTPUT ");
 | 
			
		||||
            var colidx = 0;
 | 
			
		||||
            foreach (var col in _table.Columns.Values)
 | 
			
		||||
            {
 | 
			
		||||
                if (colidx > 0) sb.Append(", ");
 | 
			
		||||
                sb.Append("DELETED.").Append(_commonUtils.QuoteReadColumn(col.Attribute.MapType, _commonUtils.QuoteSqlName(col.Attribute.Name))).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName));
 | 
			
		||||
                ++colidx;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var validx = sql.IndexOf(" WHERE ");
 | 
			
		||||
            if (validx == -1) throw new ArgumentException("找不到 WHERE ");
 | 
			
		||||
            sb.Insert(0, sql.Substring(0, validx));
 | 
			
		||||
            sb.Append(sql.Substring(validx));
 | 
			
		||||
 | 
			
		||||
            sql = sb.ToString();
 | 
			
		||||
            var dbParms = _params.ToArray();
 | 
			
		||||
            var before = new Aop.CurdBeforeEventArgs(_table.Type, Aop.CurdType.Delete, sql, dbParms);
 | 
			
		||||
            _orm.Aop.CurdBefore?.Invoke(this, before);
 | 
			
		||||
            var ret = new List<T1>();
 | 
			
		||||
            Exception exception = null;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                ret = await _orm.Ado.QueryAsync<T1>(_connection, _transaction, CommandType.Text, sql, dbParms);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                exception = ex;
 | 
			
		||||
                throw ex;
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                var after = new Aop.CurdAfterEventArgs(before, exception, ret);
 | 
			
		||||
                _orm.Aop.CurdAfter?.Invoke(this, after);
 | 
			
		||||
            }
 | 
			
		||||
            this.ClearData();
 | 
			
		||||
            return ret;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,164 @@
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using SafeObjectPool;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data;
 | 
			
		||||
using System.Data.Common;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.SqlServer
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    class OdbcSqlServerInsert<T1> : Internal.CommonProvider.InsertProvider<T1> where T1 : class
 | 
			
		||||
    {
 | 
			
		||||
        public OdbcSqlServerInsert(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression)
 | 
			
		||||
            : base(orm, commonUtils, commonExpression)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override int ExecuteAffrows() => base.SplitExecuteAffrows(1000, 2100);
 | 
			
		||||
        public override Task<int> ExecuteAffrowsAsync() => base.SplitExecuteAffrowsAsync(1000, 2100);
 | 
			
		||||
        public override long ExecuteIdentity() => base.SplitExecuteIdentity(1000, 2100);
 | 
			
		||||
        public override Task<long> ExecuteIdentityAsync() => base.SplitExecuteIdentityAsync(1000, 2100);
 | 
			
		||||
        public override List<T1> ExecuteInserted() => base.SplitExecuteInserted(1000, 2100);
 | 
			
		||||
        public override Task<List<T1>> ExecuteInsertedAsync() => base.SplitExecuteInsertedAsync(1000, 2100);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        protected override long RawExecuteIdentity()
 | 
			
		||||
        {
 | 
			
		||||
            var sql = this.ToSql();
 | 
			
		||||
            if (string.IsNullOrEmpty(sql)) return 0;
 | 
			
		||||
 | 
			
		||||
            sql = string.Concat(sql, "; SELECT SCOPE_IDENTITY();");
 | 
			
		||||
            var before = new Aop.CurdBeforeEventArgs(_table.Type, Aop.CurdType.Insert, sql, _params);
 | 
			
		||||
            _orm.Aop.CurdBefore?.Invoke(this, before);
 | 
			
		||||
            long ret = 0;
 | 
			
		||||
            Exception exception = null;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                long.TryParse(string.Concat(_orm.Ado.ExecuteScalar(_connection, _transaction, CommandType.Text, sql, _params)), out ret);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                exception = ex;
 | 
			
		||||
                throw ex;
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                var after = new Aop.CurdAfterEventArgs(before, exception, ret);
 | 
			
		||||
                _orm.Aop.CurdAfter?.Invoke(this, after);
 | 
			
		||||
            }
 | 
			
		||||
            return ret;
 | 
			
		||||
        }
 | 
			
		||||
        async protected override Task<long> RawExecuteIdentityAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var sql = this.ToSql();
 | 
			
		||||
            if (string.IsNullOrEmpty(sql)) return 0;
 | 
			
		||||
 | 
			
		||||
            sql = string.Concat(sql, "; SELECT SCOPE_IDENTITY();");
 | 
			
		||||
            var before = new Aop.CurdBeforeEventArgs(_table.Type, Aop.CurdType.Insert, sql, _params);
 | 
			
		||||
            _orm.Aop.CurdBefore?.Invoke(this, before);
 | 
			
		||||
            long ret = 0;
 | 
			
		||||
            Exception exception = null;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                long.TryParse(string.Concat(await _orm.Ado.ExecuteScalarAsync(_connection, _transaction, CommandType.Text, sql, _params)), out ret);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                exception = ex;
 | 
			
		||||
                throw ex;
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                var after = new Aop.CurdAfterEventArgs(before, exception, ret);
 | 
			
		||||
                _orm.Aop.CurdAfter?.Invoke(this, after);
 | 
			
		||||
            }
 | 
			
		||||
            return ret;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override List<T1> RawExecuteInserted()
 | 
			
		||||
        {
 | 
			
		||||
            var sql = this.ToSql();
 | 
			
		||||
            if (string.IsNullOrEmpty(sql)) return new List<T1>();
 | 
			
		||||
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            sb.Append(" OUTPUT ");
 | 
			
		||||
            var colidx = 0;
 | 
			
		||||
            foreach (var col in _table.Columns.Values)
 | 
			
		||||
            {
 | 
			
		||||
                if (colidx > 0) sb.Append(", ");
 | 
			
		||||
                sb.Append(_commonUtils.QuoteReadColumn(col.Attribute.MapType, $"INSERTED.{_commonUtils.QuoteSqlName(col.Attribute.Name)}")).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName));
 | 
			
		||||
                ++colidx;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var validx = sql.IndexOf(") VALUES");
 | 
			
		||||
            if (validx == -1) throw new ArgumentException("找不到 VALUES");
 | 
			
		||||
            sb.Insert(0, sql.Substring(0, validx + 1));
 | 
			
		||||
            sb.Append(sql.Substring(validx + 1));
 | 
			
		||||
 | 
			
		||||
            sql = sb.ToString();
 | 
			
		||||
            var before = new Aop.CurdBeforeEventArgs(_table.Type, Aop.CurdType.Insert, sql, _params);
 | 
			
		||||
            _orm.Aop.CurdBefore?.Invoke(this, before);
 | 
			
		||||
            var ret = new List<T1>();
 | 
			
		||||
            Exception exception = null;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                ret = _orm.Ado.Query<T1>(_connection, _transaction, CommandType.Text, sql, _params);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                exception = ex;
 | 
			
		||||
                throw ex;
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                var after = new Aop.CurdAfterEventArgs(before, exception, ret);
 | 
			
		||||
                _orm.Aop.CurdAfter?.Invoke(this, after);
 | 
			
		||||
            }
 | 
			
		||||
            return ret;
 | 
			
		||||
        }
 | 
			
		||||
        async protected override Task<List<T1>> RawExecuteInsertedAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var sql = this.ToSql();
 | 
			
		||||
            if (string.IsNullOrEmpty(sql)) return new List<T1>();
 | 
			
		||||
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            sb.Append(" OUTPUT ");
 | 
			
		||||
            var colidx = 0;
 | 
			
		||||
            foreach (var col in _table.Columns.Values)
 | 
			
		||||
            {
 | 
			
		||||
                if (colidx > 0) sb.Append(", ");
 | 
			
		||||
                sb.Append(_commonUtils.QuoteReadColumn(col.Attribute.MapType, $"INSERTED.{_commonUtils.QuoteSqlName(col.Attribute.Name)}")).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName));
 | 
			
		||||
                ++colidx;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var validx = sql.IndexOf(") VALUES");
 | 
			
		||||
            if (validx == -1) throw new ArgumentException("找不到 VALUES");
 | 
			
		||||
            sb.Insert(0, sql.Substring(0, validx + 1));
 | 
			
		||||
            sb.Append(sql.Substring(validx + 1));
 | 
			
		||||
 | 
			
		||||
            sql = sb.ToString();
 | 
			
		||||
            var before = new Aop.CurdBeforeEventArgs(_table.Type, Aop.CurdType.Insert, sql, _params);
 | 
			
		||||
            _orm.Aop.CurdBefore?.Invoke(this, before);
 | 
			
		||||
            var ret = new List<T1>();
 | 
			
		||||
            Exception exception = null;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                ret = await _orm.Ado.QueryAsync<T1>(_connection, _transaction, CommandType.Text, sql, _params);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                exception = ex;
 | 
			
		||||
                throw ex;
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                var after = new Aop.CurdAfterEventArgs(before, exception, ret);
 | 
			
		||||
                _orm.Aop.CurdAfter?.Invoke(this, after);
 | 
			
		||||
            }
 | 
			
		||||
            return ret;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,310 @@
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using FreeSql.Internal.Model;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Linq.Expressions;
 | 
			
		||||
using System.Text;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.SqlServer
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    class OdbcSqlServerSelect<T1> : FreeSql.Internal.CommonProvider.Select1Provider<T1> where T1 : class
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        internal static string ToSqlStatic(CommonUtils _commonUtils, CommonExpression _commonExpression, string _select, bool _distinct, string field, StringBuilder _join, StringBuilder _where, string _groupby, string _having, string _orderby, int _skip, int _limit, List<SelectTableInfo> _tables, List<Dictionary<Type, string>> tbUnions, List<LambdaExpression> _whereCascadeExpression, IFreeSql _orm)
 | 
			
		||||
            => (_commonUtils as OdbcSqlServerUtils).IsSelectRowNumber ?
 | 
			
		||||
            ToSqlStaticRowNumber(_commonUtils, _commonExpression, _select, _distinct, field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, tbUnions, _whereCascadeExpression, _orm) :
 | 
			
		||||
            ToSqlStaticOffsetFetchNext(_commonUtils, _commonExpression, _select, _distinct, field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, tbUnions, _whereCascadeExpression, _orm);
 | 
			
		||||
 | 
			
		||||
        #region SqlServer 2005 row_number
 | 
			
		||||
        internal static string ToSqlStaticRowNumber(CommonUtils _commonUtils, CommonExpression _commonExpression, string _select, bool _distinct, string field, StringBuilder _join, StringBuilder _where, string _groupby, string _having, string _orderby, int _skip, int _limit, List<SelectTableInfo> _tables, List<Dictionary<Type, string>> tbUnions, List<LambdaExpression> _whereCascadeExpression, IFreeSql _orm)
 | 
			
		||||
        {
 | 
			
		||||
            if (_orm.CodeFirst.IsAutoSyncStructure)
 | 
			
		||||
                _orm.CodeFirst.SyncStructure(_tables.Select(a => a.Table.Type).ToArray());
 | 
			
		||||
 | 
			
		||||
            if (_whereCascadeExpression.Any())
 | 
			
		||||
                foreach (var tb in _tables.Where(a => a.Type != SelectTableInfoType.Parent))
 | 
			
		||||
                    tb.Cascade = _commonExpression.GetWhereCascadeSql(tb, _whereCascadeExpression);
 | 
			
		||||
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            var tbUnionsGt0 = tbUnions.Count > 1;
 | 
			
		||||
            for (var tbUnionsIdx = 0; tbUnionsIdx < tbUnions.Count; tbUnionsIdx++)
 | 
			
		||||
            {
 | 
			
		||||
                if (tbUnionsIdx > 0) sb.Append(" \r\n\r\nUNION ALL\r\n\r\n");
 | 
			
		||||
                if (tbUnionsGt0) sb.Append("select * from (");
 | 
			
		||||
                var tbUnion = tbUnions[tbUnionsIdx];
 | 
			
		||||
 | 
			
		||||
                var sbnav = new StringBuilder();
 | 
			
		||||
                sb.Append(_select);
 | 
			
		||||
                if (_distinct) sb.Append("DISTINCT ");
 | 
			
		||||
                if (_limit > 0) sb.Append("TOP ").Append(_skip + _limit).Append(" ");
 | 
			
		||||
                sb.Append(field);
 | 
			
		||||
                if (_skip > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    if (string.IsNullOrEmpty(_orderby))
 | 
			
		||||
                    {
 | 
			
		||||
                        var pktb = _tables.Where(a => a.Table.Primarys.Any()).FirstOrDefault();
 | 
			
		||||
                        if (pktb != null) _orderby = string.Concat(" \r\nORDER BY ", pktb.Alias, ".", _commonUtils.QuoteSqlName(pktb?.Table.Primarys.First().Attribute.Name));
 | 
			
		||||
                        else _orderby = string.Concat(" \r\nORDER BY ", _tables.First().Alias, ".", _commonUtils.QuoteSqlName(_tables.First().Table.Columns.First().Value.Attribute.Name));
 | 
			
		||||
                    }
 | 
			
		||||
                    sb.Append(", ROW_NUMBER() OVER(").Append(_orderby).Append(") AS __rownum__");
 | 
			
		||||
                }
 | 
			
		||||
                sb.Append(" \r\nFROM ");
 | 
			
		||||
                var tbsjoin = _tables.Where(a => a.Type != SelectTableInfoType.From).ToArray();
 | 
			
		||||
                var tbsfrom = _tables.Where(a => a.Type == SelectTableInfoType.From).ToArray();
 | 
			
		||||
                for (var a = 0; a < tbsfrom.Length; a++)
 | 
			
		||||
                {
 | 
			
		||||
                    sb.Append(_commonUtils.QuoteSqlName(tbUnion[tbsfrom[a].Table.Type])).Append(" ").Append(tbsfrom[a].Alias);
 | 
			
		||||
                    if (tbsjoin.Length > 0)
 | 
			
		||||
                    {
 | 
			
		||||
                        //如果存在 join 查询,则处理 from t1, t2 改为 from t1 inner join t2 on 1 = 1
 | 
			
		||||
                        for (var b = 1; b < tbsfrom.Length; b++)
 | 
			
		||||
                        {
 | 
			
		||||
                            sb.Append(" \r\nLEFT JOIN ").Append(_commonUtils.QuoteSqlName(tbUnion[tbsfrom[b].Table.Type])).Append(" ").Append(tbsfrom[b].Alias);
 | 
			
		||||
 | 
			
		||||
                            if (string.IsNullOrEmpty(tbsfrom[b].NavigateCondition) && string.IsNullOrEmpty(tbsfrom[b].On) && string.IsNullOrEmpty(tbsfrom[b].Cascade)) sb.Append(" ON 1 = 1");
 | 
			
		||||
                            else
 | 
			
		||||
                            {
 | 
			
		||||
                                sb.Append(" ON ").Append(tbsfrom[b].NavigateCondition ?? tbsfrom[b].On);
 | 
			
		||||
                                if (string.IsNullOrEmpty(tbsfrom[b].Cascade) == false) sb.Append(" AND (").Append(tbsfrom[b].Cascade).Append(")");
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        if (!string.IsNullOrEmpty(tbsfrom[a].NavigateCondition)) sbnav.Append(" AND (").Append(tbsfrom[a].NavigateCondition).Append(")");
 | 
			
		||||
                        if (!string.IsNullOrEmpty(tbsfrom[a].On)) sbnav.Append(" AND (").Append(tbsfrom[a].On).Append(")");
 | 
			
		||||
                        if (a > 0 && !string.IsNullOrEmpty(tbsfrom[a].Cascade)) sbnav.Append(" AND (").Append(tbsfrom[a].Cascade).Append(")");
 | 
			
		||||
                    }
 | 
			
		||||
                    if (a < tbsfrom.Length - 1) sb.Append(", ");
 | 
			
		||||
                }
 | 
			
		||||
                foreach (var tb in tbsjoin)
 | 
			
		||||
                {
 | 
			
		||||
                    if (tb.Type == SelectTableInfoType.Parent) continue;
 | 
			
		||||
                    switch (tb.Type)
 | 
			
		||||
                    {
 | 
			
		||||
                        case SelectTableInfoType.LeftJoin:
 | 
			
		||||
                            sb.Append(" \r\nLEFT JOIN ");
 | 
			
		||||
                            break;
 | 
			
		||||
                        case SelectTableInfoType.InnerJoin:
 | 
			
		||||
                            sb.Append(" \r\nINNER JOIN ");
 | 
			
		||||
                            break;
 | 
			
		||||
                        case SelectTableInfoType.RightJoin:
 | 
			
		||||
                            sb.Append(" \r\nRIGHT JOIN ");
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                    sb.Append(_commonUtils.QuoteSqlName(tbUnion[tb.Table.Type])).Append(" ").Append(tb.Alias).Append(" ON ").Append(tb.On ?? tb.NavigateCondition);
 | 
			
		||||
                    if (!string.IsNullOrEmpty(tb.Cascade)) sb.Append(" AND (").Append(tb.Cascade).Append(")");
 | 
			
		||||
                    if (!string.IsNullOrEmpty(tb.On) && !string.IsNullOrEmpty(tb.NavigateCondition)) sbnav.Append(" AND (").Append(tb.NavigateCondition).Append(")");
 | 
			
		||||
                }
 | 
			
		||||
                if (_join.Length > 0) sb.Append(_join);
 | 
			
		||||
 | 
			
		||||
                sbnav.Append(_where);
 | 
			
		||||
                if (!string.IsNullOrEmpty(_tables[0].Cascade))
 | 
			
		||||
                    sbnav.Append(" AND (").Append(_tables[0].Cascade).Append(")");
 | 
			
		||||
 | 
			
		||||
                foreach (var tb in _tables)
 | 
			
		||||
                {
 | 
			
		||||
                    if (tb.Type == SelectTableInfoType.Parent) continue;
 | 
			
		||||
                    if (string.IsNullOrEmpty(tb.Table.SelectFilter) == false)
 | 
			
		||||
                        sbnav.Append(" AND (").Append(tb.Table.SelectFilter.Replace("a.", $"{tb.Alias}.")).Append(")");
 | 
			
		||||
                }
 | 
			
		||||
                if (sbnav.Length > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    sb.Append(" \r\nWHERE ").Append(sbnav.Remove(0, 5));
 | 
			
		||||
                }
 | 
			
		||||
                if (string.IsNullOrEmpty(_groupby) == false)
 | 
			
		||||
                {
 | 
			
		||||
                    sb.Append(_groupby);
 | 
			
		||||
                    if (string.IsNullOrEmpty(_having) == false)
 | 
			
		||||
                        sb.Append(" \r\nHAVING ").Append(_having.Substring(5));
 | 
			
		||||
                }
 | 
			
		||||
                if (_skip <= 0)
 | 
			
		||||
                    sb.Append(_orderby);
 | 
			
		||||
                else
 | 
			
		||||
                    sb.Insert(0, "WITH t AS ( ").Append(" ) SELECT t.* FROM t where __rownum__ > ").Append(_skip);
 | 
			
		||||
 | 
			
		||||
                sbnav.Clear();
 | 
			
		||||
                if (tbUnionsGt0) sb.Append(") ftb");
 | 
			
		||||
            }
 | 
			
		||||
            return sb.ToString();
 | 
			
		||||
        }
 | 
			
		||||
        #endregion
 | 
			
		||||
 | 
			
		||||
        #region SqlServer 2012+ offset feach next
 | 
			
		||||
        internal static string ToSqlStaticOffsetFetchNext(CommonUtils _commonUtils, CommonExpression _commonExpression, string _select, bool _distinct, string field, StringBuilder _join, StringBuilder _where, string _groupby, string _having, string _orderby, int _skip, int _limit, List<SelectTableInfo> _tables, List<Dictionary<Type, string>> tbUnions, List<LambdaExpression> _whereCascadeExpression, IFreeSql _orm)
 | 
			
		||||
        {
 | 
			
		||||
            if (_orm.CodeFirst.IsAutoSyncStructure)
 | 
			
		||||
                _orm.CodeFirst.SyncStructure(_tables.Select(a => a.Table.Type).ToArray());
 | 
			
		||||
 | 
			
		||||
            if (_whereCascadeExpression.Any())
 | 
			
		||||
                foreach (var tb in _tables.Where(a => a.Type != SelectTableInfoType.Parent))
 | 
			
		||||
                    tb.Cascade = _commonExpression.GetWhereCascadeSql(tb, _whereCascadeExpression);
 | 
			
		||||
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            var tbUnionsGt0 = tbUnions.Count > 1;
 | 
			
		||||
            for (var tbUnionsIdx = 0; tbUnionsIdx < tbUnions.Count; tbUnionsIdx++)
 | 
			
		||||
            {
 | 
			
		||||
                if (tbUnionsIdx > 0) sb.Append(" \r\n\r\nUNION ALL\r\n\r\n");
 | 
			
		||||
                if (tbUnionsGt0) sb.Append("select * from (");
 | 
			
		||||
                var tbUnion = tbUnions[tbUnionsIdx];
 | 
			
		||||
                
 | 
			
		||||
                var sbnav = new StringBuilder();
 | 
			
		||||
                sb.Append(_select);
 | 
			
		||||
                if (_distinct) sb.Append("DISTINCT ");
 | 
			
		||||
                if (_skip <= 0 && _limit > 0) sb.Append("TOP ").Append(_limit).Append(" ");
 | 
			
		||||
                sb.Append(field);
 | 
			
		||||
                sb.Append(" \r\nFROM ");
 | 
			
		||||
                var tbsjoin = _tables.Where(a => a.Type != SelectTableInfoType.From).ToArray();
 | 
			
		||||
                var tbsfrom = _tables.Where(a => a.Type == SelectTableInfoType.From).ToArray();
 | 
			
		||||
                for (var a = 0; a < tbsfrom.Length; a++)
 | 
			
		||||
                {
 | 
			
		||||
                    sb.Append(_commonUtils.QuoteSqlName(tbUnion[tbsfrom[a].Table.Type])).Append(" ").Append(tbsfrom[a].Alias);
 | 
			
		||||
                    if (tbsjoin.Length > 0)
 | 
			
		||||
                    {
 | 
			
		||||
                        //如果存在 join 查询,则处理 from t1, t2 改为 from t1 inner join t2 on 1 = 1
 | 
			
		||||
                        for (var b = 1; b < tbsfrom.Length; b++)
 | 
			
		||||
                        {
 | 
			
		||||
                            sb.Append(" \r\nLEFT JOIN ").Append(_commonUtils.QuoteSqlName(tbUnion[tbsfrom[b].Table.Type])).Append(" ").Append(tbsfrom[b].Alias);
 | 
			
		||||
 | 
			
		||||
                            if (string.IsNullOrEmpty(tbsfrom[b].NavigateCondition) && string.IsNullOrEmpty(tbsfrom[b].On) && string.IsNullOrEmpty(tbsfrom[b].Cascade)) sb.Append(" ON 1 = 1");
 | 
			
		||||
                            else
 | 
			
		||||
                            {
 | 
			
		||||
                                sb.Append(" ON ").Append(tbsfrom[b].NavigateCondition ?? tbsfrom[b].On);
 | 
			
		||||
                                if (string.IsNullOrEmpty(tbsfrom[b].Cascade) == false) sb.Append(" AND (").Append(tbsfrom[b].Cascade).Append(")");
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        if (!string.IsNullOrEmpty(tbsfrom[a].NavigateCondition)) sbnav.Append(" AND (").Append(tbsfrom[a].NavigateCondition).Append(")");
 | 
			
		||||
                        if (!string.IsNullOrEmpty(tbsfrom[a].On)) sbnav.Append(" AND (").Append(tbsfrom[a].On).Append(")");
 | 
			
		||||
                        if (a > 0 && !string.IsNullOrEmpty(tbsfrom[a].Cascade)) sbnav.Append(" AND (").Append(tbsfrom[a].Cascade).Append(")");
 | 
			
		||||
                    }
 | 
			
		||||
                    if (a < tbsfrom.Length - 1) sb.Append(", ");
 | 
			
		||||
                }
 | 
			
		||||
                foreach (var tb in tbsjoin)
 | 
			
		||||
                {
 | 
			
		||||
                    if (tb.Type == SelectTableInfoType.Parent) continue;
 | 
			
		||||
                    switch (tb.Type)
 | 
			
		||||
                    {
 | 
			
		||||
                        case SelectTableInfoType.LeftJoin:
 | 
			
		||||
                            sb.Append(" \r\nLEFT JOIN ");
 | 
			
		||||
                            break;
 | 
			
		||||
                        case SelectTableInfoType.InnerJoin:
 | 
			
		||||
                            sb.Append(" \r\nINNER JOIN ");
 | 
			
		||||
                            break;
 | 
			
		||||
                        case SelectTableInfoType.RightJoin:
 | 
			
		||||
                            sb.Append(" \r\nRIGHT JOIN ");
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                    sb.Append(_commonUtils.QuoteSqlName(tbUnion[tb.Table.Type])).Append(" ").Append(tb.Alias).Append(" ON ").Append(tb.On ?? tb.NavigateCondition);
 | 
			
		||||
                    if (!string.IsNullOrEmpty(tb.Cascade)) sb.Append(" AND (").Append(tb.Cascade).Append(")");
 | 
			
		||||
                    if (!string.IsNullOrEmpty(tb.On) && !string.IsNullOrEmpty(tb.NavigateCondition)) sbnav.Append(" AND (").Append(tb.NavigateCondition).Append(")");
 | 
			
		||||
                }
 | 
			
		||||
                if (_join.Length > 0) sb.Append(_join);
 | 
			
		||||
 | 
			
		||||
                sbnav.Append(_where);
 | 
			
		||||
                if (!string.IsNullOrEmpty(_tables[0].Cascade))
 | 
			
		||||
                    sbnav.Append(" AND (").Append(_tables[0].Cascade).Append(")");
 | 
			
		||||
 | 
			
		||||
                foreach (var tb in _tables)
 | 
			
		||||
                {
 | 
			
		||||
                    if (tb.Type == SelectTableInfoType.Parent) continue;
 | 
			
		||||
                    if (string.IsNullOrEmpty(tb.Table.SelectFilter) == false)
 | 
			
		||||
                        sbnav.Append(" AND (").Append(tb.Table.SelectFilter.Replace("a.", $"{tb.Alias}.")).Append(")");
 | 
			
		||||
                }
 | 
			
		||||
                if (sbnav.Length > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    sb.Append(" \r\nWHERE ").Append(sbnav.Remove(0, 5));
 | 
			
		||||
                }
 | 
			
		||||
                if (string.IsNullOrEmpty(_groupby) == false)
 | 
			
		||||
                {
 | 
			
		||||
                    sb.Append(_groupby);
 | 
			
		||||
                    if (string.IsNullOrEmpty(_having) == false)
 | 
			
		||||
                        sb.Append(" \r\nHAVING ").Append(_having.Substring(5));
 | 
			
		||||
                }
 | 
			
		||||
                if (_skip > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    if (string.IsNullOrEmpty(_orderby))
 | 
			
		||||
                    {
 | 
			
		||||
                        var pktb = _tables.Where(a => a.Table.Primarys.Any()).FirstOrDefault();
 | 
			
		||||
                        if (pktb != null) _orderby = string.Concat(" \r\nORDER BY ", pktb.Alias, ".", _commonUtils.QuoteSqlName(pktb?.Table.Primarys.First().Attribute.Name));
 | 
			
		||||
                        else _orderby = string.Concat(" \r\nORDER BY ", _tables.First().Alias, ".", _commonUtils.QuoteSqlName(_tables.First().Table.Columns.First().Value.Attribute.Name));
 | 
			
		||||
                    }
 | 
			
		||||
                    sb.Append(_orderby).Append($" \r\nOFFSET {_skip} ROW");
 | 
			
		||||
                    if (_limit > 0) sb.Append($" \r\nFETCH NEXT {_limit} ROW ONLY");
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    sb.Append(_orderby);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                sbnav.Clear();
 | 
			
		||||
                if (tbUnionsGt0) sb.Append(") ftb");
 | 
			
		||||
            }
 | 
			
		||||
            return sb.ToString();
 | 
			
		||||
        }
 | 
			
		||||
        #endregion
 | 
			
		||||
 | 
			
		||||
        public OdbcSqlServerSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override ISelect<T1, T2> From<T2>(Expression<Func<ISelectFromExpression<T1>, T2, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new OdbcSqlServerSelect<T1, T2>(_orm, _commonUtils, _commonExpression, null); OdbcSqlServerSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override ISelect<T1, T2, T3> From<T2, T3>(Expression<Func<ISelectFromExpression<T1>, T2, T3, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new OdbcSqlServerSelect<T1, T2, T3>(_orm, _commonUtils, _commonExpression, null); OdbcSqlServerSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override ISelect<T1, T2, T3, T4> From<T2, T3, T4>(Expression<Func<ISelectFromExpression<T1>, T2, T3, T4, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new OdbcSqlServerSelect<T1, T2, T3, T4>(_orm, _commonUtils, _commonExpression, null); OdbcSqlServerSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override ISelect<T1, T2, T3, T4, T5> From<T2, T3, T4, T5>(Expression<Func<ISelectFromExpression<T1>, T2, T3, T4, T5, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new OdbcSqlServerSelect<T1, T2, T3, T4, T5>(_orm, _commonUtils, _commonExpression, null); OdbcSqlServerSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override ISelect<T1, T2, T3, T4, T5, T6> From<T2, T3, T4, T5, T6>(Expression<Func<ISelectFromExpression<T1>, T2, T3, T4, T5, T6, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new OdbcSqlServerSelect<T1, T2, T3, T4, T5, T6>(_orm, _commonUtils, _commonExpression, null); OdbcSqlServerSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override ISelect<T1, T2, T3, T4, T5, T6, T7> From<T2, T3, T4, T5, T6, T7>(Expression<Func<ISelectFromExpression<T1>, T2, T3, T4, T5, T6, T7, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new OdbcSqlServerSelect<T1, T2, T3, T4, T5, T6, T7>(_orm, _commonUtils, _commonExpression, null); OdbcSqlServerSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override ISelect<T1, T2, T3, T4, T5, T6, T7, T8> From<T2, T3, T4, T5, T6, T7, T8>(Expression<Func<ISelectFromExpression<T1>, T2, T3, T4, T5, T6, T7, T8, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new OdbcSqlServerSelect<T1, T2, T3, T4, T5, T6, T7, T8>(_orm, _commonUtils, _commonExpression, null); OdbcSqlServerSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override ISelect<T1, T2, T3, T4, T5, T6, T7, T8, T9> From<T2, T3, T4, T5, T6, T7, T8, T9>(Expression<Func<ISelectFromExpression<T1>, T2, T3, T4, T5, T6, T7, T8, T9, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new OdbcSqlServerSelect<T1, T2, T3, T4, T5, T6, T7, T8, T9>(_orm, _commonUtils, _commonExpression, null); OdbcSqlServerSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override ISelect<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> From<T2, T3, T4, T5, T6, T7, T8, T9, T10>(Expression<Func<ISelectFromExpression<T1>, T2, T3, T4, T5, T6, T7, T8, T9, T10, ISelectFromExpression<T1>>> exp) { this.InternalFrom(exp); var ret = new OdbcSqlServerSelect<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(_orm, _commonUtils, _commonExpression, null); OdbcSqlServerSelect<T1>.CopyData(this, ret, exp?.Parameters); return ret; }
 | 
			
		||||
        public override string ToSql(string field = null) => ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class OdbcSqlServerSelect<T1, T2> : FreeSql.Internal.CommonProvider.Select2Provider<T1, T2> where T1 : class where T2 : class
 | 
			
		||||
    {
 | 
			
		||||
        public OdbcSqlServerSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcSqlServerSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class OdbcSqlServerSelect<T1, T2, T3> : FreeSql.Internal.CommonProvider.Select3Provider<T1, T2, T3> where T1 : class where T2 : class where T3 : class
 | 
			
		||||
    {
 | 
			
		||||
        public OdbcSqlServerSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcSqlServerSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class OdbcSqlServerSelect<T1, T2, T3, T4> : FreeSql.Internal.CommonProvider.Select4Provider<T1, T2, T3, T4> where T1 : class where T2 : class where T3 : class where T4 : class
 | 
			
		||||
    {
 | 
			
		||||
        public OdbcSqlServerSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcSqlServerSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class OdbcSqlServerSelect<T1, T2, T3, T4, T5> : FreeSql.Internal.CommonProvider.Select5Provider<T1, T2, T3, T4, T5> where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class
 | 
			
		||||
    {
 | 
			
		||||
        public OdbcSqlServerSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcSqlServerSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class OdbcSqlServerSelect<T1, T2, T3, T4, T5, T6> : FreeSql.Internal.CommonProvider.Select6Provider<T1, T2, T3, T4, T5, T6> where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class
 | 
			
		||||
    {
 | 
			
		||||
        public OdbcSqlServerSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcSqlServerSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class OdbcSqlServerSelect<T1, T2, T3, T4, T5, T6, T7> : FreeSql.Internal.CommonProvider.Select7Provider<T1, T2, T3, T4, T5, T6, T7> where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class
 | 
			
		||||
    {
 | 
			
		||||
        public OdbcSqlServerSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcSqlServerSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class OdbcSqlServerSelect<T1, T2, T3, T4, T5, T6, T7, T8> : FreeSql.Internal.CommonProvider.Select8Provider<T1, T2, T3, T4, T5, T6, T7, T8> where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class
 | 
			
		||||
    {
 | 
			
		||||
        public OdbcSqlServerSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcSqlServerSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class OdbcSqlServerSelect<T1, T2, T3, T4, T5, T6, T7, T8, T9> : FreeSql.Internal.CommonProvider.Select9Provider<T1, T2, T3, T4, T5, T6, T7, T8, T9> where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class where T9 : class
 | 
			
		||||
    {
 | 
			
		||||
        public OdbcSqlServerSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcSqlServerSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
    class OdbcSqlServerSelect<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> : FreeSql.Internal.CommonProvider.Select10Provider<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class where T9 : class where T10 : class
 | 
			
		||||
    {
 | 
			
		||||
        public OdbcSqlServerSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { }
 | 
			
		||||
        public override string ToSql(string field = null) => OdbcSqlServerSelect<T1>.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _whereCascadeExpression, _orm);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,148 @@
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using FreeSql.Internal.Model;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.SqlServer
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    class OdbcSqlServerUpdate<T1> : Internal.CommonProvider.UpdateProvider<T1> where T1 : class
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        public OdbcSqlServerUpdate(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere)
 | 
			
		||||
            : base(orm, commonUtils, commonExpression, dywhere)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override int ExecuteAffrows() => base.SplitExecuteAffrows(500, 2100);
 | 
			
		||||
        public override Task<int> ExecuteAffrowsAsync() => base.SplitExecuteAffrowsAsync(500, 2100);
 | 
			
		||||
        public override List<T1> ExecuteUpdated() => base.SplitExecuteUpdated(500, 2100);
 | 
			
		||||
        public override Task<List<T1>> ExecuteUpdatedAsync() => base.SplitExecuteUpdatedAsync(500, 2100);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        protected override List<T1> RawExecuteUpdated()
 | 
			
		||||
        {
 | 
			
		||||
            var sql = this.ToSql();
 | 
			
		||||
            if (string.IsNullOrEmpty(sql)) return new List<T1>();
 | 
			
		||||
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            sb.Append(" OUTPUT ");
 | 
			
		||||
            var colidx = 0;
 | 
			
		||||
            foreach (var col in _table.Columns.Values)
 | 
			
		||||
            {
 | 
			
		||||
                if (colidx > 0) sb.Append(", ");
 | 
			
		||||
                sb.Append(_commonUtils.QuoteReadColumn(col.Attribute.MapType, $"INSERTED.{_commonUtils.QuoteSqlName(col.Attribute.Name)}")).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName));
 | 
			
		||||
                ++colidx;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var validx = sql.IndexOf(" \r\nWHERE ");
 | 
			
		||||
            if (validx == -1) throw new ArgumentException("找不到 WHERE ");
 | 
			
		||||
            sb.Insert(0, sql.Substring(0, validx));
 | 
			
		||||
            sb.Append(sql.Substring(validx));
 | 
			
		||||
 | 
			
		||||
            sql = sb.ToString();
 | 
			
		||||
            var dbParms = _params.Concat(_paramsSource).ToArray();
 | 
			
		||||
            var before = new Aop.CurdBeforeEventArgs(_table.Type, Aop.CurdType.Update, sql, dbParms);
 | 
			
		||||
            _orm.Aop.CurdBefore?.Invoke(this, before);
 | 
			
		||||
            var ret = new List<T1>();
 | 
			
		||||
            Exception exception = null;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                ret = _orm.Ado.Query<T1>(_connection, _transaction, CommandType.Text, sql, dbParms);
 | 
			
		||||
                ValidateVersionAndThrow(ret.Count);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                exception = ex;
 | 
			
		||||
                throw ex;
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                var after = new Aop.CurdAfterEventArgs(before, exception, ret);
 | 
			
		||||
                _orm.Aop.CurdAfter?.Invoke(this, after);
 | 
			
		||||
            }
 | 
			
		||||
            return ret;
 | 
			
		||||
        }
 | 
			
		||||
        async protected override Task<List<T1>> RawExecuteUpdatedAsync()
 | 
			
		||||
        {
 | 
			
		||||
            var sql = this.ToSql();
 | 
			
		||||
            if (string.IsNullOrEmpty(sql)) return new List<T1>();
 | 
			
		||||
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            sb.Append(" OUTPUT ");
 | 
			
		||||
            var colidx = 0;
 | 
			
		||||
            foreach (var col in _table.Columns.Values)
 | 
			
		||||
            {
 | 
			
		||||
                if (colidx > 0) sb.Append(", ");
 | 
			
		||||
                sb.Append(_commonUtils.QuoteReadColumn(col.Attribute.MapType, $"INSERTED.{_commonUtils.QuoteSqlName(col.Attribute.Name)}")).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName));
 | 
			
		||||
                ++colidx;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var validx = sql.IndexOf(" \r\nWHERE ");
 | 
			
		||||
            if (validx == -1) throw new ArgumentException("找不到 WHERE ");
 | 
			
		||||
            sb.Insert(0, sql.Substring(0, validx));
 | 
			
		||||
            sb.Append(sql.Substring(validx));
 | 
			
		||||
 | 
			
		||||
            sql = sb.ToString();
 | 
			
		||||
            var dbParms = _params.Concat(_paramsSource).ToArray();
 | 
			
		||||
            var before = new Aop.CurdBeforeEventArgs(_table.Type, Aop.CurdType.Update, sql, dbParms);
 | 
			
		||||
            _orm.Aop.CurdBefore?.Invoke(this, before);
 | 
			
		||||
            var ret = new List<T1>();
 | 
			
		||||
            Exception exception = null;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                ret = await _orm.Ado.QueryAsync<T1>(_connection, _transaction, CommandType.Text, sql, dbParms);
 | 
			
		||||
                ValidateVersionAndThrow(ret.Count);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                exception = ex;
 | 
			
		||||
                throw ex;
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                var after = new Aop.CurdAfterEventArgs(before, exception, ret);
 | 
			
		||||
                _orm.Aop.CurdAfter?.Invoke(this, after);
 | 
			
		||||
            }
 | 
			
		||||
            return ret;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override void ToSqlCase(StringBuilder caseWhen, ColumnInfo[] primarys)
 | 
			
		||||
        {
 | 
			
		||||
            if (_table.Primarys.Length == 1)
 | 
			
		||||
            {
 | 
			
		||||
                caseWhen.Append(_commonUtils.QuoteReadColumn(_table.Primarys.First().Attribute.MapType, _commonUtils.QuoteSqlName(_table.Primarys.First().Attribute.Name)));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            caseWhen.Append("(");
 | 
			
		||||
            var pkidx = 0;
 | 
			
		||||
            foreach (var pk in _table.Primarys)
 | 
			
		||||
            {
 | 
			
		||||
                if (pkidx > 0) caseWhen.Append(", ");
 | 
			
		||||
                caseWhen.Append("cast(").Append(_commonUtils.QuoteReadColumn(_table.Primarys.First().Attribute.MapType, _commonUtils.QuoteSqlName(pk.Attribute.Name))).Append(" as varchar)");
 | 
			
		||||
                ++pkidx;
 | 
			
		||||
            }
 | 
			
		||||
            caseWhen.Append(")");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override void ToSqlWhen(StringBuilder sb, ColumnInfo[] primarys, object d)
 | 
			
		||||
        {
 | 
			
		||||
            if (_table.Primarys.Length == 1)
 | 
			
		||||
            {
 | 
			
		||||
                sb.Append(_commonUtils.FormatSql("{0}", _table.Primarys.First().GetMapValue(d)));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            var pkidx = 0;
 | 
			
		||||
            foreach (var pk in _table.Primarys)
 | 
			
		||||
            {
 | 
			
		||||
                if (pkidx > 0) sb.Append(", ");
 | 
			
		||||
                sb.Append("cast(").Append(_commonUtils.FormatSql("{0}", pk.GetMapValue(d))).Append(" as varchar)");
 | 
			
		||||
                ++pkidx;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,79 @@
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using SafeObjectPool;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections;
 | 
			
		||||
using System.Data.Common;
 | 
			
		||||
using System.Data.Odbc;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.SqlServer
 | 
			
		||||
{
 | 
			
		||||
    class OdbcSqlServerAdo : FreeSql.Internal.CommonProvider.AdoProvider
 | 
			
		||||
    {
 | 
			
		||||
        public OdbcSqlServerAdo() : base(DataType.SqlServer) { }
 | 
			
		||||
        public OdbcSqlServerAdo(CommonUtils util, string masterConnectionString, string[] slaveConnectionStrings) : base(DataType.SqlServer)
 | 
			
		||||
        {
 | 
			
		||||
            base._util = util;
 | 
			
		||||
            if (!string.IsNullOrEmpty(masterConnectionString))
 | 
			
		||||
                MasterPool = new OdbcSqlServerConnectionPool("主库", masterConnectionString, null, null);
 | 
			
		||||
            if (slaveConnectionStrings != null)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var slaveConnectionString in slaveConnectionStrings)
 | 
			
		||||
                {
 | 
			
		||||
                    var slavePool = new OdbcSqlServerConnectionPool($"从库{SlavePools.Count + 1}", slaveConnectionString, () => Interlocked.Decrement(ref slaveUnavailables), () => Interlocked.Increment(ref slaveUnavailables));
 | 
			
		||||
                    SlavePools.Add(slavePool);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        static DateTime dt1970 = new DateTime(1970, 1, 1);
 | 
			
		||||
        public override object AddslashesProcessParam(object param, Type mapType)
 | 
			
		||||
        {
 | 
			
		||||
            if (param == null) return "NULL";
 | 
			
		||||
            if (mapType != null && mapType != param.GetType())
 | 
			
		||||
                param = Utils.GetDataReaderValue(mapType, param);
 | 
			
		||||
            if (param is bool || param is bool?)
 | 
			
		||||
                return (bool)param ? 1 : 0;
 | 
			
		||||
            else if (param is string)
 | 
			
		||||
                return string.Concat("N'", param.ToString().Replace("'", "''"), "'");
 | 
			
		||||
            else if (param is char)
 | 
			
		||||
                return string.Concat("'", param.ToString().Replace("'", "''"), "'");
 | 
			
		||||
            else if (param is Enum)
 | 
			
		||||
                return ((Enum)param).ToInt64();
 | 
			
		||||
            else if (decimal.TryParse(string.Concat(param), out var trydec))
 | 
			
		||||
                return param;
 | 
			
		||||
            else if (param is DateTime || param is DateTime?)
 | 
			
		||||
            {
 | 
			
		||||
                if (param.Equals(DateTime.MinValue) == true) param = new DateTime(1970, 1, 1);
 | 
			
		||||
                return string.Concat("'", ((DateTime)param).ToString("yyyy-MM-dd HH:mm:ss.fff"), "'");
 | 
			
		||||
            }
 | 
			
		||||
            else if (param is DateTimeOffset || param is DateTimeOffset?)
 | 
			
		||||
            {
 | 
			
		||||
                if (param.Equals(DateTimeOffset.MinValue) == true) param = new DateTimeOffset(new DateTime(1970, 1, 1), TimeSpan.Zero);
 | 
			
		||||
                return string.Concat("'", ((DateTimeOffset)param).ToString("yyyy-MM-dd HH:mm:ss.fff zzzz"), "'");
 | 
			
		||||
            }
 | 
			
		||||
            else if (param is TimeSpan || param is TimeSpan?)
 | 
			
		||||
                return ((TimeSpan)param).TotalSeconds;
 | 
			
		||||
            else if (param is IEnumerable)
 | 
			
		||||
            {
 | 
			
		||||
                var sb = new StringBuilder();
 | 
			
		||||
                var ie = param as IEnumerable;
 | 
			
		||||
                foreach (var z in ie) sb.Append(",").Append(AddslashesProcessParam(z, mapType));
 | 
			
		||||
                return sb.Length == 0 ? "(NULL)" : sb.Remove(0, 1).Insert(0, "(").Append(")").ToString();
 | 
			
		||||
            }
 | 
			
		||||
            return string.Concat("'", param.ToString().Replace("'", "''"), "'");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override DbCommand CreateCommand()
 | 
			
		||||
        {
 | 
			
		||||
            return new OdbcCommand();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override void ReturnConnection(ObjectPool<DbConnection> pool, Object<DbConnection> conn, Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            (pool as OdbcSqlServerConnectionPool).Return(conn, ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override DbParameter[] GetDbParamtersByObject(string sql, object obj) => _util.GetDbParamtersByObject(sql, obj);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,236 @@
 | 
			
		||||
using SafeObjectPool;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data;
 | 
			
		||||
using System.Data.Common;
 | 
			
		||||
using System.Data.Odbc;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.SqlServer
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    class OdbcSqlServerConnectionPool : ObjectPool<DbConnection>
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        internal Action availableHandler;
 | 
			
		||||
        internal Action unavailableHandler;
 | 
			
		||||
 | 
			
		||||
        public OdbcSqlServerConnectionPool(string name, string connectionString, Action availableHandler, Action unavailableHandler) : base(null)
 | 
			
		||||
        {
 | 
			
		||||
            var policy = new OdbcSqlServerConnectionPoolPolicy
 | 
			
		||||
            {
 | 
			
		||||
                _pool = this,
 | 
			
		||||
                Name = name
 | 
			
		||||
            };
 | 
			
		||||
            this.Policy = policy;
 | 
			
		||||
            policy.ConnectionString = connectionString;
 | 
			
		||||
 | 
			
		||||
            this.availableHandler = availableHandler;
 | 
			
		||||
            this.unavailableHandler = unavailableHandler;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Return(Object<DbConnection> obj, Exception exception, bool isRecreate = false)
 | 
			
		||||
        {
 | 
			
		||||
            if (exception != null && exception is OdbcException)
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
                if (obj.Value.Ping() == false)
 | 
			
		||||
                {
 | 
			
		||||
 | 
			
		||||
                    base.SetUnavailable(exception);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            base.Return(obj, isRecreate);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    class OdbcSqlServerConnectionPoolPolicy : IPolicy<DbConnection>
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        internal OdbcSqlServerConnectionPool _pool;
 | 
			
		||||
        public string Name { get; set; } = "SqlServer OdbcConnection 对象池";
 | 
			
		||||
        public int PoolSize { get; set; } = 100;
 | 
			
		||||
        public TimeSpan SyncGetTimeout { get; set; } = TimeSpan.FromSeconds(10);
 | 
			
		||||
        public TimeSpan IdleTimeout { get; set; } = TimeSpan.Zero;
 | 
			
		||||
        public int AsyncGetCapacity { get; set; } = 10000;
 | 
			
		||||
        public bool IsThrowGetTimeoutException { get; set; } = true;
 | 
			
		||||
        public int CheckAvailableInterval { get; set; } = 5;
 | 
			
		||||
 | 
			
		||||
        static ConcurrentDictionary<string, int> dicConnStrIncr = new ConcurrentDictionary<string, int>(StringComparer.CurrentCultureIgnoreCase);
 | 
			
		||||
        private string _connectionString;
 | 
			
		||||
        public string ConnectionString
 | 
			
		||||
        {
 | 
			
		||||
            get => _connectionString;
 | 
			
		||||
            set
 | 
			
		||||
            {
 | 
			
		||||
                _connectionString = value ?? "";
 | 
			
		||||
 | 
			
		||||
                var pattern = @"Max\s*pool\s*size\s*=\s*(\d+)";
 | 
			
		||||
                Match m = Regex.Match(_connectionString, pattern, RegexOptions.IgnoreCase);
 | 
			
		||||
                if (m.Success == false || int.TryParse(m.Groups[1].Value, out var poolsize) == false || poolsize <= 0) poolsize = 100;
 | 
			
		||||
                var connStrIncr = dicConnStrIncr.AddOrUpdate(_connectionString, 1, (oldkey, oldval) => oldval + 1);
 | 
			
		||||
                PoolSize = poolsize + connStrIncr;
 | 
			
		||||
                _connectionString = m.Success ?
 | 
			
		||||
                    Regex.Replace(_connectionString, pattern, $"Max pool size={PoolSize}", RegexOptions.IgnoreCase) :
 | 
			
		||||
                    $"{_connectionString};Max pool size={PoolSize}";
 | 
			
		||||
 | 
			
		||||
                pattern = @"Connection\s*LifeTime\s*=\s*(\d+)";
 | 
			
		||||
                m = Regex.Match(_connectionString, pattern, RegexOptions.IgnoreCase);
 | 
			
		||||
                if (m.Success)
 | 
			
		||||
                {
 | 
			
		||||
                    IdleTimeout = TimeSpan.FromSeconds(int.Parse(m.Groups[1].Value));
 | 
			
		||||
                    _connectionString = Regex.Replace(_connectionString, pattern, "", RegexOptions.IgnoreCase);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var minPoolSize = 0;
 | 
			
		||||
                pattern = @"Min\s*pool\s*size\s*=\s*(\d+)";
 | 
			
		||||
                m = Regex.Match(_connectionString, pattern, RegexOptions.IgnoreCase);
 | 
			
		||||
                if (m.Success)
 | 
			
		||||
                {
 | 
			
		||||
                    minPoolSize = int.Parse(m.Groups[1].Value);
 | 
			
		||||
                    _connectionString = Regex.Replace(_connectionString, pattern, "", RegexOptions.IgnoreCase);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                FreeSql.Internal.CommonUtils.PrevReheatConnectionPool(_pool, minPoolSize);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool OnCheckAvailable(Object<DbConnection> obj)
 | 
			
		||||
        {
 | 
			
		||||
            if (obj.Value.State == ConnectionState.Closed) obj.Value.Open();
 | 
			
		||||
            return obj.Value.Ping(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public DbConnection OnCreate()
 | 
			
		||||
        {
 | 
			
		||||
            var conn = new OdbcConnection(_connectionString);
 | 
			
		||||
            return conn;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnDestroy(DbConnection obj)
 | 
			
		||||
        {
 | 
			
		||||
            if (obj.State != ConnectionState.Closed) obj.Close();
 | 
			
		||||
            obj.Dispose();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnGet(Object<DbConnection> obj)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            if (_pool.IsAvailable)
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
                if (obj.Value == null)
 | 
			
		||||
                {
 | 
			
		||||
                    if (_pool.SetUnavailable(new Exception("连接字符串错误")) == true)
 | 
			
		||||
                        throw new Exception($"【{this.Name}】连接字符串错误,请检查。");
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (obj.Value.State != ConnectionState.Open || DateTime.Now.Subtract(obj.LastReturnTime).TotalSeconds > 60 && obj.Value.Ping() == false)
 | 
			
		||||
                {
 | 
			
		||||
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        obj.Value.Open();
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (Exception ex)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (_pool.SetUnavailable(ex) == true)
 | 
			
		||||
                            throw new Exception($"【{this.Name}】状态不可用,等待后台检查程序恢复方可使用。{ex.Message}");
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        async public Task OnGetAsync(Object<DbConnection> obj)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            if (_pool.IsAvailable)
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
                if (obj.Value == null)
 | 
			
		||||
                {
 | 
			
		||||
                    if (_pool.SetUnavailable(new Exception("连接字符串错误")) == true)
 | 
			
		||||
                        throw new Exception($"【{this.Name}】连接字符串错误,请检查。");
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (obj.Value.State != ConnectionState.Open || DateTime.Now.Subtract(obj.LastReturnTime).TotalSeconds > 60 && (await obj.Value.PingAsync()) == false)
 | 
			
		||||
                {
 | 
			
		||||
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        await obj.Value.OpenAsync();
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (Exception ex)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (_pool.SetUnavailable(ex) == true)
 | 
			
		||||
                            throw new Exception($"【{this.Name}】状态不可用,等待后台检查程序恢复方可使用。{ex.Message}");
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnGetTimeout()
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnReturn(Object<DbConnection> obj)
 | 
			
		||||
        {
 | 
			
		||||
            if (obj.Value.State != ConnectionState.Closed) try { obj.Value.Close(); } catch { }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnAvailable()
 | 
			
		||||
        {
 | 
			
		||||
            _pool.availableHandler?.Invoke();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnUnavailable()
 | 
			
		||||
        {
 | 
			
		||||
            _pool.unavailableHandler?.Invoke();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static class DbConnectionExtensions
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        static DbCommand PingCommand(DbConnection conn)
 | 
			
		||||
        {
 | 
			
		||||
            var cmd = conn.CreateCommand();
 | 
			
		||||
            cmd.CommandTimeout = 5;
 | 
			
		||||
            cmd.CommandText = "select 1";
 | 
			
		||||
            return cmd;
 | 
			
		||||
        }
 | 
			
		||||
        public static bool Ping(this DbConnection that, bool isThrow = false)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                PingCommand(that).ExecuteNonQuery();
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                if (that.State != ConnectionState.Closed) try { that.Close(); } catch { }
 | 
			
		||||
                if (isThrow) throw;
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        async public static Task<bool> PingAsync(this DbConnection that, bool isThrow = false)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                await PingCommand(that).ExecuteNonQueryAsync();
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                if (that.State != ConnectionState.Closed) try { that.Close(); } catch { }
 | 
			
		||||
                if (isThrow) throw;
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,415 @@
 | 
			
		||||
using FreeSql.DataAnnotations;
 | 
			
		||||
using FreeSql.DatabaseModel;
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using FreeSql.Internal.Model;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data;
 | 
			
		||||
using System.Data.Odbc;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.SqlServer
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    class OdbcSqlServerCodeFirst : Internal.CommonProvider.CodeFirstProvider
 | 
			
		||||
    {
 | 
			
		||||
        public override bool IsNoneCommandParameter { get => true; set => base.IsNoneCommandParameter = true; }
 | 
			
		||||
        public OdbcSqlServerCodeFirst(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) : base(orm, commonUtils, commonExpression) { }
 | 
			
		||||
 | 
			
		||||
        static object _dicCsToDbLock = new object();
 | 
			
		||||
        static Dictionary<string, (OdbcType type, string dbtype, string dbtypeFull, bool? isUnsigned, bool? isnullable, object defaultValue)> _dicCsToDb = new Dictionary<string, (OdbcType type, string dbtype, string dbtypeFull, bool? isUnsigned, bool? isnullable, object defaultValue)>() {
 | 
			
		||||
                { typeof(bool).FullName,  (OdbcType.Bit, "bit","bit NOT NULL", null, false, false) },{ typeof(bool?).FullName,  (OdbcType.Bit, "bit","bit", null, true, null) },
 | 
			
		||||
 | 
			
		||||
                { typeof(sbyte).FullName,  (OdbcType.SmallInt, "smallint", "smallint NOT NULL", false, false, 0) },{ typeof(sbyte?).FullName,  (OdbcType.SmallInt, "smallint", "smallint", false, true, null) },
 | 
			
		||||
                { typeof(short).FullName,  (OdbcType.SmallInt, "smallint","smallint NOT NULL", false, false, 0) },{ typeof(short?).FullName,  (OdbcType.SmallInt, "smallint", "smallint", false, true, null) },
 | 
			
		||||
                { typeof(int).FullName,  (OdbcType.Int, "int", "int NOT NULL", false, false, 0) },{ typeof(int?).FullName,  (OdbcType.Int, "int", "int", false, true, null) },
 | 
			
		||||
                { typeof(long).FullName,  (OdbcType.BigInt, "bigint","bigint NOT NULL", false, false, 0) },{ typeof(long?).FullName,  (OdbcType.BigInt, "bigint","bigint", false, true, null) },
 | 
			
		||||
 | 
			
		||||
                { typeof(byte).FullName,  (OdbcType.TinyInt, "tinyint","tinyint NOT NULL", true, false, 0) },{ typeof(byte?).FullName,  (OdbcType.TinyInt, "tinyint","tinyint", true, true, null) },
 | 
			
		||||
                { typeof(ushort).FullName,  (OdbcType.Int, "int","int NOT NULL", true, false, 0) },{ typeof(ushort?).FullName,  (OdbcType.Int, "int", "int", true, true, null) },
 | 
			
		||||
                { typeof(uint).FullName,  (OdbcType.BigInt, "bigint", "bigint NOT NULL", true, false, 0) },{ typeof(uint?).FullName,  (OdbcType.BigInt, "bigint", "bigint", true, true, null) },
 | 
			
		||||
                { typeof(ulong).FullName,  (OdbcType.Decimal, "decimal", "decimal(20,0) NOT NULL", true, false, 0) },{ typeof(ulong?).FullName,  (OdbcType.Decimal, "decimal", "decimal(20,0)", true, true, null) },
 | 
			
		||||
 | 
			
		||||
                { typeof(double).FullName,  (OdbcType.Double, "float", "float NOT NULL", false, false, 0) },{ typeof(double?).FullName,  (OdbcType.Double, "float", "float", false, true, null) },
 | 
			
		||||
                { typeof(float).FullName,  (OdbcType.Real, "real","real NOT NULL", false, false, 0) },{ typeof(float?).FullName,  (OdbcType.Real, "real","real", false, true, null) },
 | 
			
		||||
                { typeof(decimal).FullName,  (OdbcType.Decimal, "decimal", "decimal(10,2) NOT NULL", false, false, 0) },{ typeof(decimal?).FullName,  (OdbcType.Decimal, "decimal", "decimal(10,2)", false, true, null) },
 | 
			
		||||
 | 
			
		||||
                { typeof(TimeSpan).FullName,  (OdbcType.Time, "time","time NOT NULL", false, false, 0) },{ typeof(TimeSpan?).FullName,  (OdbcType.Time, "time", "time",false, true, null) },
 | 
			
		||||
                { typeof(DateTime).FullName,  (OdbcType.DateTime, "datetime", "datetime NOT NULL", false, false, new DateTime(1970,1,1)) },{ typeof(DateTime?).FullName,  (OdbcType.DateTime, "datetime", "datetime", false, true, null) },
 | 
			
		||||
                { typeof(DateTimeOffset).FullName,  (OdbcType.DateTime, "datetimeoffset", "datetimeoffset NOT NULL", false, false, new DateTimeOffset(new DateTime(1970,1,1), TimeSpan.Zero)) },{ typeof(DateTimeOffset?).FullName,  (OdbcType.DateTime, "datetimeoffset", "datetimeoffset", false, true, null) },
 | 
			
		||||
 | 
			
		||||
                { typeof(byte[]).FullName,  (OdbcType.VarBinary, "varbinary", "varbinary(255)", false, null, new byte[0]) },
 | 
			
		||||
                { typeof(string).FullName,  (OdbcType.NVarChar, "nvarchar", "nvarchar(255)", false, null, "") },
 | 
			
		||||
 | 
			
		||||
                { typeof(Guid).FullName,  (OdbcType.UniqueIdentifier, "uniqueidentifier", "uniqueidentifier NOT NULL", false, false, Guid.Empty) },{ typeof(Guid?).FullName,  (OdbcType.UniqueIdentifier, "uniqueidentifier", "uniqueidentifier", false, true, null) },
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
        public override (int type, string dbtype, string dbtypeFull, bool? isnullable, object defaultValue)? GetDbInfo(Type type)
 | 
			
		||||
        {
 | 
			
		||||
            if (_dicCsToDb.TryGetValue(type.FullName, out var trydc)) return new (int, string, string, bool?, object)?(((int)trydc.type, trydc.dbtype, trydc.dbtypeFull, trydc.isnullable, trydc.defaultValue));
 | 
			
		||||
            if (type.IsArray) return null;
 | 
			
		||||
            var enumType = type.IsEnum ? type : null;
 | 
			
		||||
            if (enumType == null && type.IsNullableType() && type.GenericTypeArguments.Length == 1 && type.GenericTypeArguments.First().IsEnum) enumType = type.GenericTypeArguments.First();
 | 
			
		||||
            if (enumType != null)
 | 
			
		||||
            {
 | 
			
		||||
                var newItem = enumType.GetCustomAttributes(typeof(FlagsAttribute), false).Any() ?
 | 
			
		||||
                    (OdbcType.BigInt, "bigint", $"bigint{(type.IsEnum ? " NOT NULL" : "")}", false, type.IsEnum ? false : true, Enum.GetValues(enumType).GetValue(0)) :
 | 
			
		||||
                    (OdbcType.Int, "int", $"int{(type.IsEnum ? " NOT NULL" : "")}", false, type.IsEnum ? false : true, Enum.GetValues(enumType).GetValue(0));
 | 
			
		||||
                if (_dicCsToDb.ContainsKey(type.FullName) == false)
 | 
			
		||||
                {
 | 
			
		||||
                    lock (_dicCsToDbLock)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (_dicCsToDb.ContainsKey(type.FullName) == false)
 | 
			
		||||
                            _dicCsToDb.Add(type.FullName, newItem);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                return ((int)newItem.Item1, newItem.Item2, newItem.Item3, newItem.Item5, newItem.Item6);
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void AddOrUpdateMS_Description(StringBuilder sb, string schema, string table, string column, string comment)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrEmpty(comment))
 | 
			
		||||
            {
 | 
			
		||||
                sb.AppendFormat(@"
 | 
			
		||||
IF ((SELECT COUNT(1) from fn_listextendedproperty('MS_Description', 
 | 
			
		||||
  'SCHEMA', N'{0}', 
 | 
			
		||||
  'TABLE', N'{1}', 
 | 
			
		||||
  'COLUMN', N'{2}')) > 0) 
 | 
			
		||||
  EXEC sp_dropextendedproperty @name = N'MS_Description'
 | 
			
		||||
    , @level0type = 'SCHEMA', @level0name = N'{0}'
 | 
			
		||||
    , @level1type = 'TABLE', @level1name = N'{1}'
 | 
			
		||||
    , @level2type = 'COLUMN', @level2name = N'{2}'
 | 
			
		||||
", schema.Replace("'", "''"), table.Replace("'", "''"), column.Replace("'", "''"));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            sb.AppendFormat(@"
 | 
			
		||||
IF ((SELECT COUNT(1) from fn_listextendedproperty('MS_Description', 
 | 
			
		||||
  'SCHEMA', N'{0}', 
 | 
			
		||||
  'TABLE', N'{1}', 
 | 
			
		||||
  'COLUMN', N'{2}')) > 0) 
 | 
			
		||||
  EXEC sp_updateextendedproperty @name = N'MS_Description', @value = N'{3}'
 | 
			
		||||
    , @level0type = 'SCHEMA', @level0name = N'{0}'
 | 
			
		||||
    , @level1type = 'TABLE', @level1name = N'{1}'
 | 
			
		||||
    , @level2type = 'COLUMN', @level2name = N'{2}'
 | 
			
		||||
ELSE
 | 
			
		||||
  EXEC sp_addextendedproperty @name = N'MS_Description', @value = N'{3}'
 | 
			
		||||
    , @level0type = 'SCHEMA', @level0name = N'{0}'
 | 
			
		||||
    , @level1type = 'TABLE', @level1name = N'{1}'
 | 
			
		||||
    , @level2type = 'COLUMN', @level2name = N'{2}'
 | 
			
		||||
", schema.Replace("'", "''"), table.Replace("'", "''"), column.Replace("'", "''"), comment?.Replace("'", "''") ?? "");
 | 
			
		||||
        }
 | 
			
		||||
        public override string GetComparisonDDLStatements(params Type[] entityTypes)
 | 
			
		||||
        {
 | 
			
		||||
            var conn = _orm.Ado.MasterPool.Get(TimeSpan.FromSeconds(5));
 | 
			
		||||
            var database = conn.Value.Database;
 | 
			
		||||
            Func<string, string, object> ExecuteScalar = (db, sql) =>
 | 
			
		||||
            {
 | 
			
		||||
                if (string.Compare(database, db) != 0) conn.Value.ChangeDatabase(db);
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    using (var cmd = conn.Value.CreateCommand())
 | 
			
		||||
                    {
 | 
			
		||||
                        cmd.CommandText = sql;
 | 
			
		||||
                        cmd.CommandType = CommandType.Text;
 | 
			
		||||
                        return cmd.ExecuteScalar();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                finally
 | 
			
		||||
                {
 | 
			
		||||
                    if (string.Compare(database, db) != 0) conn.Value.ChangeDatabase(database);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var entityType in entityTypes)
 | 
			
		||||
                {
 | 
			
		||||
                    if (sb.Length > 0) sb.Append("\r\n");
 | 
			
		||||
                    var tb = _commonUtils.GetTableByEntity(entityType);
 | 
			
		||||
                    if (tb == null) throw new Exception($"类型 {entityType.FullName} 不可迁移");
 | 
			
		||||
                    if (tb.Columns.Any() == false) throw new Exception($"类型 {entityType.FullName} 不可迁移,可迁移属性0个");
 | 
			
		||||
                    var tbname = tb.DbName.Split(new[] { '.' }, 3);
 | 
			
		||||
                    if (tbname?.Length == 1) tbname = new[] { database, "dbo", tbname[0] };
 | 
			
		||||
                    if (tbname?.Length == 2) tbname = new[] { database, tbname[0], tbname[1] };
 | 
			
		||||
 | 
			
		||||
                    var tboldname = tb.DbOldName?.Split(new[] { '.' }, 3); //旧表名
 | 
			
		||||
                    if (tboldname?.Length == 1) tboldname = new[] { database, "dbo", tboldname[0] };
 | 
			
		||||
                    if (tboldname?.Length == 2) tboldname = new[] { database, tboldname[0], tboldname[1] };
 | 
			
		||||
 | 
			
		||||
                    if (string.Compare(tbname[0], database, true) != 0 && ExecuteScalar(database, $" select 1 from sys.databases where name='{tbname[0]}'") == null) //创建数据库
 | 
			
		||||
                        ExecuteScalar(database, $"if not exists(select 1 from sys.databases where name='{tbname[0]}')\r\n\tcreate database [{tbname[0]}];");
 | 
			
		||||
                    if (string.Compare(tbname[1], "dbo", true) != 0 && ExecuteScalar(tbname[0], $" select 1 from sys.schemas where name='{tbname[1]}'") == null) //创建模式
 | 
			
		||||
                        ExecuteScalar(tbname[0], $"create schema [{tbname[1]}] authorization [dbo]");
 | 
			
		||||
 | 
			
		||||
                    var sbalter = new StringBuilder();
 | 
			
		||||
                    var istmpatler = false; //创建临时表,导入数据,删除旧表,修改
 | 
			
		||||
                    if (ExecuteScalar(tbname[0], $" select 1 from dbo.sysobjects where id = object_id(N'[{tbname[1]}].[{tbname[2]}]') and OBJECTPROPERTY(id, N'IsUserTable') = 1") == null)
 | 
			
		||||
                    { //表不存在
 | 
			
		||||
                        if (tboldname != null)
 | 
			
		||||
                        {
 | 
			
		||||
                            if (string.Compare(tboldname[0], tbname[0], true) != 0 && ExecuteScalar(database, $" select 1 from sys.databases where name='{tboldname[0]}'") == null ||
 | 
			
		||||
                                string.Compare(tboldname[1], tbname[1], true) != 0 && ExecuteScalar(tboldname[0], $" select 1 from sys.schemas where name='{tboldname[1]}'") == null ||
 | 
			
		||||
                                ExecuteScalar(tboldname[0], $" select 1 from dbo.sysobjects where id = object_id(N'[{tboldname[1]}].[{tboldname[2]}]') and OBJECTPROPERTY(id, N'IsUserTable') = 1") == null)
 | 
			
		||||
                                //数据库或模式或表不存在
 | 
			
		||||
                                tboldname = null;
 | 
			
		||||
                        }
 | 
			
		||||
                        if (tboldname == null)
 | 
			
		||||
                        {
 | 
			
		||||
                            //创建新表
 | 
			
		||||
                            sb.Append("use ").Append(_commonUtils.QuoteSqlName(tbname[0])).Append(";\r\nCREATE TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[1]}.{tbname[2]}")).Append(" ( ");
 | 
			
		||||
                            var pkidx = 0;
 | 
			
		||||
                            foreach (var tbcol in tb.ColumnsByPosition)
 | 
			
		||||
                            {
 | 
			
		||||
                                sb.Append(" \r\n  ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ");
 | 
			
		||||
                                sb.Append(tbcol.Attribute.DbType);
 | 
			
		||||
                                if (tbcol.Attribute.IsIdentity == true && tbcol.Attribute.DbType.IndexOf("identity", StringComparison.CurrentCultureIgnoreCase) == -1) sb.Append(" identity(1,1)");
 | 
			
		||||
                                if (tbcol.Attribute.IsPrimary == true)
 | 
			
		||||
                                {
 | 
			
		||||
                                    if (tb.Primarys.Length > 1)
 | 
			
		||||
                                    {
 | 
			
		||||
                                        if (pkidx == tb.Primarys.Length - 1)
 | 
			
		||||
                                            sb.Append(" primary key (").Append(string.Join(", ", tb.Primarys.Select(a => _commonUtils.QuoteSqlName(a.Attribute.Name)))).Append(")");
 | 
			
		||||
                                    }
 | 
			
		||||
                                    else
 | 
			
		||||
                                        sb.Append(" primary key");
 | 
			
		||||
                                    pkidx++;
 | 
			
		||||
                                }
 | 
			
		||||
                                sb.Append(",");
 | 
			
		||||
                            }
 | 
			
		||||
                            foreach (var uk in tb.Uniques)
 | 
			
		||||
                            {
 | 
			
		||||
                                sb.Append(" \r\n  CONSTRAINT ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append(" UNIQUE(");
 | 
			
		||||
                                foreach (var tbcol in uk.Value) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", ");
 | 
			
		||||
                                sb.Remove(sb.Length - 2, 2).Append("),");
 | 
			
		||||
                            }
 | 
			
		||||
                            sb.Remove(sb.Length - 1, 1).Append("\r\n);\r\n");
 | 
			
		||||
                            //备注
 | 
			
		||||
                            foreach (var tbcol in tb.ColumnsByPosition)
 | 
			
		||||
                            {
 | 
			
		||||
                                if (string.IsNullOrEmpty(tbcol.Comment) == false)
 | 
			
		||||
                                    AddOrUpdateMS_Description(sb, tbname[1], tbname[2], tbcol.Attribute.Name, tbcol.Comment);
 | 
			
		||||
                            }
 | 
			
		||||
                            continue;
 | 
			
		||||
                        }
 | 
			
		||||
                        //如果新表,旧表在一个数据库和模式下,直接修改表名
 | 
			
		||||
                        if (string.Compare(tbname[0], tboldname[0], true) == 0 &&
 | 
			
		||||
                            string.Compare(tbname[1], tboldname[1], true) == 0)
 | 
			
		||||
                            sbalter.Append("use ").Append(_commonUtils.QuoteSqlName(tbname[0])).Append(_commonUtils.FormatSql(";\r\nEXEC sp_rename {0}, {1};\r\n", _commonUtils.QuoteSqlName($"{tboldname[0]}.{tboldname[1]}.{tboldname[2]}"), tbname[2]));
 | 
			
		||||
                        else
 | 
			
		||||
                        {
 | 
			
		||||
                            //如果新表,旧表不在一起,创建新表,导入数据,删除旧表
 | 
			
		||||
                            istmpatler = true;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                        tboldname = null; //如果新表已经存在,不走改表名逻辑
 | 
			
		||||
 | 
			
		||||
                    //对比字段,只可以修改类型、增加字段、有限的修改字段名;保证安全不删除字段
 | 
			
		||||
                    var sql = string.Format(@"
 | 
			
		||||
use [{0}];
 | 
			
		||||
select
 | 
			
		||||
a.name 'Column'
 | 
			
		||||
,b.name + case 
 | 
			
		||||
 when b.name in ('Char', 'VarChar', 'NChar', 'NVarChar', 'Binary', 'VarBinary') then '(' + 
 | 
			
		||||
  case when a.max_length = -1 then 'MAX' 
 | 
			
		||||
  when b.name in ('NChar', 'NVarchar') then cast(a.max_length / 2 as varchar)
 | 
			
		||||
  else cast(a.max_length as varchar) end + ')'
 | 
			
		||||
 when b.name in ('Numeric', 'Decimal') then '(' + cast(a.precision as varchar) + ',' + cast(a.scale as varchar) + ')'
 | 
			
		||||
 else '' end as 'SqlType'
 | 
			
		||||
,case when a.is_nullable = 1 then '1' else '0' end 'IsNullable'
 | 
			
		||||
,case when a.is_identity = 1 then '1' else '0' end 'IsIdentity'
 | 
			
		||||
,c.value
 | 
			
		||||
from sys.columns a
 | 
			
		||||
inner join sys.types b on b.user_type_id = a.user_type_id
 | 
			
		||||
left join sys.extended_properties AS c ON c.major_id = a.object_id AND c.minor_id = a.column_id
 | 
			
		||||
left join sys.tables d on d.object_id = a.object_id
 | 
			
		||||
left join sys.schemas e on e.schema_id = d.schema_id
 | 
			
		||||
where a.object_id in (object_id(N'[{1}].[{2}]'));
 | 
			
		||||
use " + database, tboldname ?? tbname);
 | 
			
		||||
                    var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql);
 | 
			
		||||
                    var tbstruct = ds.ToDictionary(a => string.Concat(a[0]), a => new
 | 
			
		||||
                    {
 | 
			
		||||
                        column = string.Concat(a[0]),
 | 
			
		||||
                        sqlType = string.Concat(a[1]),
 | 
			
		||||
                        is_nullable = string.Concat(a[2]) == "1",
 | 
			
		||||
                        is_identity = string.Concat(a[3]) == "1",
 | 
			
		||||
                        comment = string.Concat(a[4])
 | 
			
		||||
                    }, StringComparer.CurrentCultureIgnoreCase);
 | 
			
		||||
 | 
			
		||||
                    if (istmpatler == false)
 | 
			
		||||
                    {
 | 
			
		||||
                        foreach (var tbcol in tb.ColumnsByPosition)
 | 
			
		||||
                        {
 | 
			
		||||
                            if (tbstruct.TryGetValue(tbcol.Attribute.Name, out var tbstructcol) ||
 | 
			
		||||
                                string.IsNullOrEmpty(tbcol.Attribute.OldName) == false && tbstruct.TryGetValue(tbcol.Attribute.OldName, out tbstructcol))
 | 
			
		||||
                            {
 | 
			
		||||
                                var isCommentChanged = tbstructcol.comment != (tbcol.Comment ?? "");
 | 
			
		||||
                                if (tbcol.Attribute.DbType.StartsWith(tbstructcol.sqlType, StringComparison.CurrentCultureIgnoreCase) == false ||
 | 
			
		||||
                                    tbcol.Attribute.IsNullable != tbstructcol.is_nullable ||
 | 
			
		||||
                                    tbcol.Attribute.IsIdentity != tbstructcol.is_identity)
 | 
			
		||||
                                {
 | 
			
		||||
                                    istmpatler = true;
 | 
			
		||||
                                    break;
 | 
			
		||||
                                }
 | 
			
		||||
                                if (string.Compare(tbstructcol.column, tbcol.Attribute.OldName, true) == 0)
 | 
			
		||||
                                    //修改列名
 | 
			
		||||
                                    sbalter.Append(_commonUtils.FormatSql("EXEC sp_rename {0}, {1}, 'COLUMN';\r\n", $"{tbname[0]}.{tbname[1]}.{tbname[2]}.{tbstructcol.column}", tbcol.Attribute.Name));
 | 
			
		||||
                                if (isCommentChanged)
 | 
			
		||||
                                    //修改备备注
 | 
			
		||||
                                    AddOrUpdateMS_Description(sbalter, tbname[1], tbname[2], tbcol.Attribute.Name, tbcol.Comment);
 | 
			
		||||
                                continue;
 | 
			
		||||
                            }
 | 
			
		||||
                            //添加列
 | 
			
		||||
                            sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}.{tbname[2]}")).Append(" ADD ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ").Append(tbcol.Attribute.DbType);
 | 
			
		||||
                            if (tbcol.Attribute.IsIdentity == true && tbcol.Attribute.DbType.IndexOf("identity", StringComparison.CurrentCultureIgnoreCase) == -1) sbalter.Append(" identity(1,1)");
 | 
			
		||||
                            if (tbcol.Attribute.IsNullable == false)
 | 
			
		||||
                            {
 | 
			
		||||
                                var addcoldbdefault = tbcol.Attribute.DbDefautValue;
 | 
			
		||||
                                if (addcoldbdefault != null) sbalter.Append(_commonUtils.FormatSql(" default({0})", addcoldbdefault));
 | 
			
		||||
                            }
 | 
			
		||||
                            sbalter.Append(";\r\n");
 | 
			
		||||
                            if (string.IsNullOrEmpty(tbcol.Comment) == false) AddOrUpdateMS_Description(sbalter, tbname[1], tbname[2], tbcol.Attribute.Name, tbcol.Comment);
 | 
			
		||||
                        }
 | 
			
		||||
                        var dsuksql = string.Format(@"
 | 
			
		||||
use [{0}];
 | 
			
		||||
select 
 | 
			
		||||
c.name
 | 
			
		||||
,d.name
 | 
			
		||||
from sys.index_columns a
 | 
			
		||||
inner join sys.indexes b on b.object_id = a.object_id and b.index_id = a.index_id
 | 
			
		||||
left join sys.columns c on c.object_id = a.object_id and c.column_id = a.column_id
 | 
			
		||||
left join sys.key_constraints d on d.parent_object_id = b.object_id and d.unique_index_id = b.index_id
 | 
			
		||||
where a.object_id in (object_id(N'[{1}].[{2}]')) and b.is_unique = 1;
 | 
			
		||||
use " + database, tboldname ?? tbname);
 | 
			
		||||
                        var dsuk = _orm.Ado.ExecuteArray(CommandType.Text, dsuksql).Select(a => new[] { string.Concat(a[0]), string.Concat(a[1]) });
 | 
			
		||||
                        foreach (var uk in tb.Uniques)
 | 
			
		||||
                        {
 | 
			
		||||
                            if (string.IsNullOrEmpty(uk.Key) || uk.Value.Any() == false) continue;
 | 
			
		||||
                            var dsukfind1 = dsuk.Where(a => string.Compare(a[1], uk.Key, true) == 0).ToArray();
 | 
			
		||||
                            if (dsukfind1.Any() == false || dsukfind1.Length != uk.Value.Count || dsukfind1.Where(a => uk.Value.Where(b => string.Compare(b.Attribute.Name, a[0], true) == 0).Any()).Count() != uk.Value.Count)
 | 
			
		||||
                            {
 | 
			
		||||
                                if (dsukfind1.Any()) sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}.{tbname[2]}")).Append(" DROP CONSTRAINT ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append(";\r\n");
 | 
			
		||||
                                sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}.{tbname[2]}")).Append(" ADD CONSTRAINT ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append(" UNIQUE(");
 | 
			
		||||
                                foreach (var tbcol in uk.Value) sbalter.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", ");
 | 
			
		||||
                                sbalter.Remove(sbalter.Length - 2, 2).Append(");\r\n");
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    if (istmpatler == false)
 | 
			
		||||
                    {
 | 
			
		||||
                        sb.Append(sbalter).Append("\r\nuse " + database);
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                    //创建临时表,数据导进临时表,然后删除原表,将临时表改名为原表名
 | 
			
		||||
                    bool idents = false;
 | 
			
		||||
                    var tablename = tboldname == null ? _commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}.{tbname[2]}") : _commonUtils.QuoteSqlName($"{tboldname[0]}.{tboldname[1]}.{tboldname[2]}");
 | 
			
		||||
                    var tmptablename = _commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}.FreeSqlTmp_{tbname[2]}");
 | 
			
		||||
                    sb.Append("BEGIN TRANSACTION\r\n")
 | 
			
		||||
                        .Append("SET QUOTED_IDENTIFIER ON\r\n")
 | 
			
		||||
                        .Append("SET ARITHABORT ON\r\n")
 | 
			
		||||
                        .Append("SET NUMERIC_ROUNDABORT OFF\r\n")
 | 
			
		||||
                        .Append("SET CONCAT_NULL_YIELDS_NULL ON\r\n")
 | 
			
		||||
                        .Append("SET ANSI_NULLS ON\r\n")
 | 
			
		||||
                        .Append("SET ANSI_PADDING ON\r\n")
 | 
			
		||||
                        .Append("SET ANSI_WARNINGS ON\r\n")
 | 
			
		||||
                        .Append("COMMIT\r\n");
 | 
			
		||||
                    sb.Append("BEGIN TRANSACTION;\r\n");
 | 
			
		||||
                    //创建临时表
 | 
			
		||||
                    sb.Append("CREATE TABLE ").Append(tmptablename).Append(" ( ");
 | 
			
		||||
                    var pkidx2 = 0;
 | 
			
		||||
                    foreach (var tbcol in tb.ColumnsByPosition)
 | 
			
		||||
                    {
 | 
			
		||||
                        sb.Append(" \r\n  ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ");
 | 
			
		||||
                        sb.Append(tbcol.Attribute.DbType);
 | 
			
		||||
                        if (tbcol.Attribute.IsIdentity == true && tbcol.Attribute.DbType.IndexOf("identity", StringComparison.CurrentCultureIgnoreCase) == -1) sb.Append(" identity(1,1)");
 | 
			
		||||
                        if (tbcol.Attribute.IsPrimary == true)
 | 
			
		||||
                        {
 | 
			
		||||
                            if (tb.Primarys.Length > 1)
 | 
			
		||||
                            {
 | 
			
		||||
                                if (pkidx2 == tb.Primarys.Length - 1)
 | 
			
		||||
                                    sb.Append(" primary key (").Append(string.Join(", ", tb.Primarys.Select(a => _commonUtils.QuoteSqlName(a.Attribute.Name)))).Append(")");
 | 
			
		||||
                            }
 | 
			
		||||
                            else
 | 
			
		||||
                                sb.Append(" primary key");
 | 
			
		||||
                            pkidx2++;
 | 
			
		||||
                        }
 | 
			
		||||
                        sb.Append(",");
 | 
			
		||||
                        idents = idents || tbcol.Attribute.IsIdentity == true;
 | 
			
		||||
                    }
 | 
			
		||||
                    foreach (var uk in tb.Uniques)
 | 
			
		||||
                    {
 | 
			
		||||
                        sb.Append(" \r\n  CONSTRAINT ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append(" UNIQUE(");
 | 
			
		||||
                        foreach (var tbcol in uk.Value) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", ");
 | 
			
		||||
                        sb.Remove(sb.Length - 2, 2).Append("),");
 | 
			
		||||
                    }
 | 
			
		||||
                    sb.Remove(sb.Length - 1, 1).Append("\r\n);\r\n");
 | 
			
		||||
                    //备注
 | 
			
		||||
                    foreach (var tbcol in tb.ColumnsByPosition)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (string.IsNullOrEmpty(tbcol.Comment) == false)
 | 
			
		||||
                            AddOrUpdateMS_Description(sb, tbname[1], $"FreeSqlTmp_{tbname[2]}", tbcol.Attribute.Name, tbcol.Comment);
 | 
			
		||||
                    }
 | 
			
		||||
                    sb.Append("ALTER TABLE ").Append(tmptablename).Append(" SET (LOCK_ESCALATION = TABLE);\r\n");
 | 
			
		||||
                    if (idents) sb.Append("SET IDENTITY_INSERT ").Append(tmptablename).Append(" ON;\r\n");
 | 
			
		||||
                    sb.Append("IF EXISTS(SELECT 1 FROM ").Append(tablename).Append(")\r\n");
 | 
			
		||||
                    sb.Append("\tEXEC('INSERT INTO ").Append(tmptablename).Append(" (");
 | 
			
		||||
                    foreach (var tbcol in tb.ColumnsByPosition)
 | 
			
		||||
                        sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", ");
 | 
			
		||||
                    sb.Remove(sb.Length - 2, 2).Append(")\r\n\t\tSELECT ");
 | 
			
		||||
                    foreach (var tbcol in tb.ColumnsByPosition)
 | 
			
		||||
                    {
 | 
			
		||||
                        var insertvalue = "NULL";
 | 
			
		||||
                        if (tbstruct.TryGetValue(tbcol.Attribute.Name, out var tbstructcol) ||
 | 
			
		||||
                            string.IsNullOrEmpty(tbcol.Attribute.OldName) == false && tbstruct.TryGetValue(tbcol.Attribute.OldName, out tbstructcol))
 | 
			
		||||
                        {
 | 
			
		||||
                            insertvalue = _commonUtils.QuoteSqlName(tbstructcol.column);
 | 
			
		||||
                            if (tbcol.Attribute.DbType.StartsWith(tbstructcol.sqlType, StringComparison.CurrentCultureIgnoreCase) == false)
 | 
			
		||||
                                insertvalue = $"cast({insertvalue} as {tbcol.Attribute.DbType.Split(' ').First()})";
 | 
			
		||||
                            if (tbcol.Attribute.IsNullable != tbstructcol.is_nullable)
 | 
			
		||||
                                insertvalue = $"isnull({insertvalue},{_commonUtils.FormatSql("{0}", GetTransferDbDefaultValue(tbcol))})";
 | 
			
		||||
                        }
 | 
			
		||||
                        else if (tbcol.Attribute.IsNullable == false)
 | 
			
		||||
                            insertvalue = _commonUtils.FormatSql("{0}", GetTransferDbDefaultValue(tbcol));
 | 
			
		||||
                        sb.Append(insertvalue.Replace("'", "''")).Append(", ");
 | 
			
		||||
                    }
 | 
			
		||||
                    sb.Remove(sb.Length - 2, 2).Append(" FROM ").Append(tablename).Append(" WITH (HOLDLOCK TABLOCKX)');\r\n");
 | 
			
		||||
                    if (idents) sb.Append("SET IDENTITY_INSERT ").Append(tmptablename).Append(" OFF;\r\n");
 | 
			
		||||
                    sb.Append("DROP TABLE ").Append(tablename).Append(";\r\n");
 | 
			
		||||
                    sb.Append("EXECUTE sp_rename N'").Append(tmptablename).Append("', N'").Append(tbname[2]).Append("', 'OBJECT';\r\n");
 | 
			
		||||
                    sb.Append("COMMIT;\r\n");
 | 
			
		||||
                }
 | 
			
		||||
                return sb.Length == 0 ? null : sb.ToString();
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    conn.Value.ChangeDatabase(database);
 | 
			
		||||
                    _orm.Ado.MasterPool.Return(conn);
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
                {
 | 
			
		||||
                    _orm.Ado.MasterPool.Return(conn, true);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        object GetTransferDbDefaultValue(ColumnInfo col)
 | 
			
		||||
        {
 | 
			
		||||
            var ddv = col.Attribute.DbDefautValue;
 | 
			
		||||
            if (ddv == null) return ddv;
 | 
			
		||||
            if (ddv is DateTime || ddv is DateTime?)
 | 
			
		||||
            {
 | 
			
		||||
                var dt = (DateTime)ddv;
 | 
			
		||||
                if (col.Attribute.DbType.Contains("SMALLDATETIME") && dt < new DateTime(1900, 1, 1)) ddv = new DateTime(1900, 1, 1);
 | 
			
		||||
                else if (col.Attribute.DbType.Contains("DATETIME") && dt < new DateTime(1753, 1, 1)) ddv = new DateTime(1753, 1, 1);
 | 
			
		||||
                else if (col.Attribute.DbType.Contains("DATE") && dt < new DateTime(0001, 1, 1)) ddv = new DateTime(0001, 1, 1);
 | 
			
		||||
            }
 | 
			
		||||
            return ddv;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,447 @@
 | 
			
		||||
using FreeSql.DatabaseModel;
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data;
 | 
			
		||||
using System.Data.Odbc;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.SqlServer
 | 
			
		||||
{
 | 
			
		||||
    class OdbcSqlServerDbFirst : IDbFirst
 | 
			
		||||
    {
 | 
			
		||||
        IFreeSql _orm;
 | 
			
		||||
        protected CommonUtils _commonUtils;
 | 
			
		||||
        protected CommonExpression _commonExpression;
 | 
			
		||||
        public OdbcSqlServerDbFirst(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression)
 | 
			
		||||
        {
 | 
			
		||||
            _orm = orm;
 | 
			
		||||
            _commonUtils = commonUtils;
 | 
			
		||||
            _commonExpression = commonExpression;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public int GetDbType(DbColumnInfo column) => (int)GetSqlDbType(column);
 | 
			
		||||
        OdbcType GetSqlDbType(DbColumnInfo column)
 | 
			
		||||
        {
 | 
			
		||||
            switch (column.DbTypeText.ToLower())
 | 
			
		||||
            {
 | 
			
		||||
                case "bit": return OdbcType.Bit;
 | 
			
		||||
                case "tinyint": return OdbcType.TinyInt;
 | 
			
		||||
                case "smallint": return OdbcType.SmallInt;
 | 
			
		||||
                case "int": return OdbcType.Int;
 | 
			
		||||
                case "bigint": return OdbcType.BigInt;
 | 
			
		||||
                case "numeric":
 | 
			
		||||
                case "decimal": return OdbcType.Decimal;
 | 
			
		||||
                case "smallmoney": return OdbcType.Decimal;
 | 
			
		||||
                case "money": return OdbcType.Decimal;
 | 
			
		||||
                case "float": return OdbcType.Double;
 | 
			
		||||
                case "real": return OdbcType.Real;
 | 
			
		||||
                case "date": return OdbcType.Date;
 | 
			
		||||
                case "datetime":
 | 
			
		||||
                case "datetime2": return OdbcType.DateTime;
 | 
			
		||||
                case "datetimeoffset": return OdbcType.DateTime;
 | 
			
		||||
                case "smalldatetime": return OdbcType.SmallDateTime;
 | 
			
		||||
                case "time": return OdbcType.Time;
 | 
			
		||||
                case "char": return OdbcType.Char;
 | 
			
		||||
                case "varchar": return OdbcType.VarChar;
 | 
			
		||||
                case "text": return OdbcType.Text;
 | 
			
		||||
                case "nchar": return OdbcType.NChar;
 | 
			
		||||
                case "nvarchar": return OdbcType.NVarChar;
 | 
			
		||||
                case "ntext": return OdbcType.NText;
 | 
			
		||||
                case "binary": return OdbcType.Binary;
 | 
			
		||||
                case "varbinary": return OdbcType.VarBinary;
 | 
			
		||||
                case "image": return OdbcType.Image;
 | 
			
		||||
                case "timestamp": return OdbcType.Timestamp;
 | 
			
		||||
                case "uniqueidentifier": return OdbcType.UniqueIdentifier;
 | 
			
		||||
                default: return OdbcType.NVarChar;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        static readonly Dictionary<int, (string csConvert, string csParse, string csStringify, string csType, Type csTypeInfo, Type csNullableTypeInfo, string csTypeValue, string dataReaderMethod)> _dicDbToCs = new Dictionary<int, (string csConvert, string csParse, string csStringify, string csType, Type csTypeInfo, Type csNullableTypeInfo, string csTypeValue, string dataReaderMethod)>() {
 | 
			
		||||
                { (int)OdbcType.Bit, ("(bool?)", "{0} == \"1\"", "{0} == true ? \"1\" : \"0\"", "bool?", typeof(bool), typeof(bool?), "{0}.Value", "GetBoolean") },
 | 
			
		||||
 | 
			
		||||
                { (int)OdbcType.TinyInt, ("(byte?)", "byte.Parse({0})", "{0}.ToString()", "byte?", typeof(byte), typeof(byte?), "{0}.Value", "GetByte") },
 | 
			
		||||
                { (int)OdbcType.SmallInt, ("(short?)", "short.Parse({0})", "{0}.ToString()", "short?", typeof(short), typeof(short?), "{0}.Value", "GetInt16") },
 | 
			
		||||
                { (int)OdbcType.Int, ("(int?)", "int.Parse({0})", "{0}.ToString()", "int?", typeof(int), typeof(int?), "{0}.Value", "GetInt32") },
 | 
			
		||||
                { (int)OdbcType.BigInt, ("(long?)", "long.Parse({0})", "{0}.ToString()", "long?", typeof(long), typeof(long?), "{0}.Value", "GetInt64") },
 | 
			
		||||
 | 
			
		||||
                { (int)OdbcType.Decimal, ("(decimal?)", "decimal.Parse({0})", "{0}.ToString()", "decimal?", typeof(decimal), typeof(decimal?), "{0}.Value", "GetDecimal") },
 | 
			
		||||
                { (int)OdbcType.Double, ("(double?)", "double.Parse({0})", "{0}.ToString()", "double?", typeof(double), typeof(double?), "{0}.Value", "GetDouble") },
 | 
			
		||||
                { (int)OdbcType.Real, ("(float?)", "float.Parse({0})", "{0}.ToString()", "float?", typeof(float), typeof(float?), "{0}.Value", "GetFloat") },
 | 
			
		||||
 | 
			
		||||
                { (int)OdbcType.Time, ("(TimeSpan?)", "TimeSpan.Parse(double.Parse({0}))", "{0}.Ticks.ToString()", "TimeSpan?", typeof(TimeSpan), typeof(TimeSpan?), "{0}.Value", "GetValue") },
 | 
			
		||||
                { (int)OdbcType.Date, ("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") },
 | 
			
		||||
                { (int)OdbcType.DateTime, ("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") },
 | 
			
		||||
                { (int)OdbcType.SmallDateTime, ("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") },
 | 
			
		||||
 | 
			
		||||
                { (int)OdbcType.Binary, ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", typeof(byte[]), typeof(byte[]), "{0}", "GetValue") },
 | 
			
		||||
                { (int)OdbcType.VarBinary, ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", typeof(byte[]), typeof(byte[]), "{0}", "GetValue") },
 | 
			
		||||
                { (int)OdbcType.Image, ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", typeof(byte[]), typeof(byte[]), "{0}", "GetValue") },
 | 
			
		||||
                { (int)OdbcType.Timestamp, ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", typeof(byte[]), typeof(byte[]), "{0}", "GetValue") },
 | 
			
		||||
 | 
			
		||||
                { (int)OdbcType.Char, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") },
 | 
			
		||||
                { (int)OdbcType.VarChar, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") },
 | 
			
		||||
                { (int)OdbcType.Text, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") },
 | 
			
		||||
                { (int)OdbcType.NChar, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") },
 | 
			
		||||
                { (int)OdbcType.NVarChar, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") },
 | 
			
		||||
                { (int)OdbcType.NText, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") },
 | 
			
		||||
 | 
			
		||||
                { (int)OdbcType.UniqueIdentifier, ("(Guid?)", "Guid.Parse({0})", "{0}.ToString()", "Guid?", typeof(Guid), typeof(Guid?), "{0}.Value", "GetGuid") },
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
        public string GetCsConvert(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? (column.IsNullable ? trydc.csConvert : trydc.csConvert.Replace("?", "")) : null;
 | 
			
		||||
        public string GetCsParse(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csParse : null;
 | 
			
		||||
        public string GetCsStringify(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csStringify : null;
 | 
			
		||||
        public string GetCsType(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? (column.IsNullable ? trydc.csType : trydc.csType.Replace("?", "")) : null;
 | 
			
		||||
        public Type GetCsTypeInfo(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csTypeInfo : null;
 | 
			
		||||
        public string GetCsTypeValue(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csTypeValue : null;
 | 
			
		||||
        public string GetDataReaderMethod(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.dataReaderMethod : null;
 | 
			
		||||
 | 
			
		||||
        public List<string> GetDatabases()
 | 
			
		||||
        {
 | 
			
		||||
            var sql = @" select name from sys.databases where name not in ('master','tempdb','model','msdb')";
 | 
			
		||||
            var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql);
 | 
			
		||||
            return ds.Select(a => a.FirstOrDefault()?.ToString()).ToList();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public List<DbTableInfo> GetTablesByDatabase(params string[] database)
 | 
			
		||||
        {
 | 
			
		||||
            var olddatabase = "";
 | 
			
		||||
            using (var conn = _orm.Ado.MasterPool.Get(TimeSpan.FromSeconds(5)))
 | 
			
		||||
            {
 | 
			
		||||
                olddatabase = conn.Value.Database;
 | 
			
		||||
            }
 | 
			
		||||
            var dbs = database == null || database.Any() == false ? new[] { olddatabase } : database;
 | 
			
		||||
            var tables = new List<DbTableInfo>();
 | 
			
		||||
 | 
			
		||||
            foreach (var db in dbs)
 | 
			
		||||
            {
 | 
			
		||||
                if (string.IsNullOrEmpty(db)) continue;
 | 
			
		||||
 | 
			
		||||
                var loc1 = new List<DbTableInfo>();
 | 
			
		||||
                var loc2 = new Dictionary<int, DbTableInfo>();
 | 
			
		||||
                var loc3 = new Dictionary<int, Dictionary<string, DbColumnInfo>>();
 | 
			
		||||
 | 
			
		||||
                var sql = $@"
 | 
			
		||||
use [{db}];
 | 
			
		||||
select 
 | 
			
		||||
 a.Object_id
 | 
			
		||||
,b.name 'Owner'
 | 
			
		||||
,a.name 'Name'
 | 
			
		||||
,c.value
 | 
			
		||||
,'TABLE' type
 | 
			
		||||
from sys.tables a
 | 
			
		||||
inner join sys.schemas b on b.schema_id = a.schema_id
 | 
			
		||||
left join sys.extended_properties AS c ON c.major_id = a.object_id AND c.minor_id = 0 AND c.name = 'MS_Description'
 | 
			
		||||
where not(b.name = 'dbo' and a.name = 'sysdiagrams')
 | 
			
		||||
union all
 | 
			
		||||
select
 | 
			
		||||
 a.Object_id
 | 
			
		||||
,b.name 'Owner'
 | 
			
		||||
,a.name 'Name'
 | 
			
		||||
,c.value
 | 
			
		||||
,'VIEW' type
 | 
			
		||||
from sys.views a
 | 
			
		||||
inner join sys.schemas b on b.schema_id = a.schema_id
 | 
			
		||||
left join sys.extended_properties AS c ON c.major_id = a.object_id AND c.minor_id = 0 AND c.name = 'MS_Description'
 | 
			
		||||
union all
 | 
			
		||||
select 
 | 
			
		||||
 a.Object_id
 | 
			
		||||
,b.name 'Owner'
 | 
			
		||||
,a.name 'Name'
 | 
			
		||||
,c.value
 | 
			
		||||
,'StoreProcedure' type
 | 
			
		||||
from sys.procedures a
 | 
			
		||||
inner join sys.schemas b on b.schema_id = a.schema_id
 | 
			
		||||
left join sys.extended_properties AS c ON c.major_id = a.object_id AND c.minor_id = 0 AND c.name = 'MS_Description'
 | 
			
		||||
where a.type = 'P' and charindex('diagram', a.name) = 0
 | 
			
		||||
order by type desc, b.name, a.name
 | 
			
		||||
;
 | 
			
		||||
use [{olddatabase}];
 | 
			
		||||
";
 | 
			
		||||
                var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql);
 | 
			
		||||
                if (ds == null) return loc1;
 | 
			
		||||
 | 
			
		||||
                var loc6 = new List<int>();
 | 
			
		||||
                var loc66 = new List<int>();
 | 
			
		||||
                foreach (object[] row in ds)
 | 
			
		||||
                {
 | 
			
		||||
                    int object_id = int.Parse(string.Concat(row[0]));
 | 
			
		||||
                    var owner = string.Concat(row[1]);
 | 
			
		||||
                    var table = string.Concat(row[2]);
 | 
			
		||||
                    var comment = string.Concat(row[3]);
 | 
			
		||||
                    Enum.TryParse<DbTableType>(string.Concat(row[4]), out var type);
 | 
			
		||||
                    loc2.Add(object_id, new DbTableInfo { Id = object_id.ToString(), Schema = owner, Name = table, Comment = comment, Type = type });
 | 
			
		||||
                    loc3.Add(object_id, new Dictionary<string, DbColumnInfo>());
 | 
			
		||||
                    switch (type)
 | 
			
		||||
                    {
 | 
			
		||||
                        case DbTableType.VIEW:
 | 
			
		||||
                        case DbTableType.TABLE:
 | 
			
		||||
                            loc6.Add(object_id);
 | 
			
		||||
                            break;
 | 
			
		||||
                        case DbTableType.StoreProcedure:
 | 
			
		||||
                            loc66.Add(object_id);
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (loc6.Count == 0) return loc1;
 | 
			
		||||
                var loc8 = string.Join(",", loc6.Select(a => string.Concat(a)));
 | 
			
		||||
                var loc88 = string.Join(",", loc66.Select(a => string.Concat(a)));
 | 
			
		||||
 | 
			
		||||
                var tsql_place = @"
 | 
			
		||||
 | 
			
		||||
select 
 | 
			
		||||
isnull(e.name,'') + '.' + isnull(d.name,'')
 | 
			
		||||
,a.Object_id
 | 
			
		||||
,a.name 'Column'
 | 
			
		||||
,b.name 'Type'
 | 
			
		||||
,case
 | 
			
		||||
 when b.name in ('Text', 'NText', 'Image') then -1
 | 
			
		||||
 when b.name in ('NChar', 'NVarchar') then a.max_length / 2
 | 
			
		||||
 else a.max_length end 'Length'
 | 
			
		||||
,b.name + case 
 | 
			
		||||
 when b.name in ('Char', 'VarChar', 'NChar', 'NVarChar', 'Binary', 'VarBinary') then '(' + 
 | 
			
		||||
  case when a.max_length = -1 then 'MAX' 
 | 
			
		||||
  when b.name in ('NChar', 'NVarchar') then cast(a.max_length / 2 as varchar)
 | 
			
		||||
  else cast(a.max_length as varchar) end + ')'
 | 
			
		||||
 when b.name in ('Numeric', 'Decimal') then '(' + cast(a.precision as varchar) + ',' + cast(a.scale as varchar) + ')'
 | 
			
		||||
 else '' end as 'SqlType'
 | 
			
		||||
,c.value
 | 
			
		||||
{0} a
 | 
			
		||||
inner join sys.types b on b.user_type_id = a.user_type_id
 | 
			
		||||
left join sys.extended_properties AS c ON c.major_id = a.object_id AND c.minor_id = a.column_id
 | 
			
		||||
left join sys.tables d on d.object_id = a.object_id
 | 
			
		||||
left join sys.schemas e on e.schema_id = d.schema_id
 | 
			
		||||
where a.object_id in ({1})
 | 
			
		||||
";
 | 
			
		||||
                sql = string.Format(tsql_place, @"
 | 
			
		||||
,a.is_nullable 'IsNullable'
 | 
			
		||||
,a.is_identity 'IsIdentity'
 | 
			
		||||
from sys.columns", loc8);
 | 
			
		||||
                if (loc88.Length > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    sql += "union all" +
 | 
			
		||||
                    string.Format(tsql_place.Replace(
 | 
			
		||||
                        "left join sys.extended_properties AS c ON c.major_id = a.object_id AND c.minor_id = a.column_id",
 | 
			
		||||
                        "left join sys.extended_properties AS c ON c.major_id = a.object_id AND c.minor_id = a.parameter_id"), @"
 | 
			
		||||
,cast(0 as bit) 'IsNullable'
 | 
			
		||||
,a.is_output 'IsIdentity'
 | 
			
		||||
from sys.parameters", loc88);
 | 
			
		||||
                }
 | 
			
		||||
                sql = $"use [{db}];{sql};use [{olddatabase}]; ";
 | 
			
		||||
                ds = _orm.Ado.ExecuteArray(CommandType.Text, sql);
 | 
			
		||||
                if (ds == null) return loc1;
 | 
			
		||||
 | 
			
		||||
                foreach (object[] row in ds)
 | 
			
		||||
                {
 | 
			
		||||
                    var table_id = string.Concat(row[0]);
 | 
			
		||||
                    var object_id = int.Parse(string.Concat(row[1]));
 | 
			
		||||
                    var column = string.Concat(row[2]);
 | 
			
		||||
                    var type = string.Concat(row[3]);
 | 
			
		||||
                    var max_length = int.Parse(string.Concat(row[4]));
 | 
			
		||||
                    var sqlType = string.Concat(row[5]);
 | 
			
		||||
                    var comment = string.Concat(row[6]);
 | 
			
		||||
                    var is_nullable = bool.Parse(string.Concat(row[7]));
 | 
			
		||||
                    var is_identity = bool.Parse(string.Concat(row[8]));
 | 
			
		||||
                    if (max_length == 0) max_length = -1;
 | 
			
		||||
 | 
			
		||||
                    loc3[object_id].Add(column, new DbColumnInfo
 | 
			
		||||
                    {
 | 
			
		||||
                        Name = column,
 | 
			
		||||
                        MaxLength = max_length,
 | 
			
		||||
                        IsIdentity = is_identity,
 | 
			
		||||
                        IsNullable = is_nullable,
 | 
			
		||||
                        IsPrimary = false,
 | 
			
		||||
                        DbTypeText = type,
 | 
			
		||||
                        DbTypeTextFull = sqlType,
 | 
			
		||||
                        Table = loc2[object_id],
 | 
			
		||||
                        Coment = comment
 | 
			
		||||
                    });
 | 
			
		||||
                    loc3[object_id][column].DbType = this.GetDbType(loc3[object_id][column]);
 | 
			
		||||
                    loc3[object_id][column].CsType = this.GetCsTypeInfo(loc3[object_id][column]);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                sql = $@"
 | 
			
		||||
use [{db}];
 | 
			
		||||
select 
 | 
			
		||||
 a.object_id 'Object_id'
 | 
			
		||||
,c.name 'Column'
 | 
			
		||||
,d.name 'Index_id'
 | 
			
		||||
,b.is_unique 'IsUnique'
 | 
			
		||||
,b.is_primary_key 'IsPrimaryKey'
 | 
			
		||||
,cast(case when b.type_desc = 'CLUSTERED' then 1 else 0 end as bit) 'IsClustered'
 | 
			
		||||
,case when a.is_descending_key = 1 then 2 when a.is_descending_key = 0 then 1 else 0 end 'IsDesc'
 | 
			
		||||
from sys.index_columns a
 | 
			
		||||
inner join sys.indexes b on b.object_id = a.object_id and b.index_id = a.index_id
 | 
			
		||||
left join sys.columns c on c.object_id = a.object_id and c.column_id = a.column_id
 | 
			
		||||
left join sys.key_constraints d on d.parent_object_id = b.object_id and d.unique_index_id = b.index_id
 | 
			
		||||
where a.object_id in ({loc8})
 | 
			
		||||
;
 | 
			
		||||
use [{olddatabase}];
 | 
			
		||||
";
 | 
			
		||||
                ds = _orm.Ado.ExecuteArray(CommandType.Text, sql);
 | 
			
		||||
                if (ds == null) return loc1;
 | 
			
		||||
 | 
			
		||||
                var indexColumns = new Dictionary<int, Dictionary<string, List<DbColumnInfo>>>();
 | 
			
		||||
                var uniqueColumns = new Dictionary<int, Dictionary<string, List<DbColumnInfo>>>();
 | 
			
		||||
                foreach (object[] row in ds)
 | 
			
		||||
                {
 | 
			
		||||
                    int object_id = int.Parse(string.Concat(row[0]));
 | 
			
		||||
                    string column = string.Concat(row[1]);
 | 
			
		||||
                    string index_id = string.Concat(row[2]);
 | 
			
		||||
                    bool is_unique = bool.Parse(string.Concat(row[3]));
 | 
			
		||||
                    bool is_primary_key = bool.Parse(string.Concat(row[4]));
 | 
			
		||||
                    bool is_clustered = bool.Parse(string.Concat(row[5]));
 | 
			
		||||
                    int is_desc = int.Parse(string.Concat(row[6]));
 | 
			
		||||
 | 
			
		||||
                    if (loc3.ContainsKey(object_id) == false || loc3[object_id].ContainsKey(column) == false) continue;
 | 
			
		||||
                    DbColumnInfo loc9 = loc3[object_id][column];
 | 
			
		||||
                    if (loc9.IsPrimary == false && is_primary_key) loc9.IsPrimary = is_primary_key;
 | 
			
		||||
 | 
			
		||||
                    Dictionary<string, List<DbColumnInfo>> loc10 = null;
 | 
			
		||||
                    List<DbColumnInfo> loc11 = null;
 | 
			
		||||
                    if (!indexColumns.TryGetValue(object_id, out loc10))
 | 
			
		||||
                        indexColumns.Add(object_id, loc10 = new Dictionary<string, List<DbColumnInfo>>());
 | 
			
		||||
                    if (!loc10.TryGetValue(index_id, out loc11))
 | 
			
		||||
                        loc10.Add(index_id, loc11 = new List<DbColumnInfo>());
 | 
			
		||||
                    loc11.Add(loc9);
 | 
			
		||||
                    if (is_unique && !is_primary_key)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (!uniqueColumns.TryGetValue(object_id, out loc10))
 | 
			
		||||
                            uniqueColumns.Add(object_id, loc10 = new Dictionary<string, List<DbColumnInfo>>());
 | 
			
		||||
                        if (!loc10.TryGetValue(index_id, out loc11))
 | 
			
		||||
                            loc10.Add(index_id, loc11 = new List<DbColumnInfo>());
 | 
			
		||||
                        loc11.Add(loc9);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                foreach (var object_id in indexColumns.Keys)
 | 
			
		||||
                {
 | 
			
		||||
                    foreach (var column in indexColumns[object_id])
 | 
			
		||||
                        loc2[object_id].IndexesDict.Add(column.Key, column.Value);
 | 
			
		||||
                }
 | 
			
		||||
                foreach (var object_id in uniqueColumns.Keys)
 | 
			
		||||
                {
 | 
			
		||||
                    foreach (var column in uniqueColumns[object_id])
 | 
			
		||||
                    {
 | 
			
		||||
                        column.Value.Sort((c1, c2) => c1.Name.CompareTo(c2.Name));
 | 
			
		||||
                        loc2[object_id].UniquesDict.Add(column.Key, column.Value);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                sql = $@"
 | 
			
		||||
use [{db}];
 | 
			
		||||
select 
 | 
			
		||||
 b.object_id 'Object_id'
 | 
			
		||||
,c.name 'Column'
 | 
			
		||||
,e.name 'FKId'
 | 
			
		||||
,a.referenced_object_id
 | 
			
		||||
,cast(1 as bit) 'IsForeignKey'
 | 
			
		||||
,d.name 'Referenced_Column'
 | 
			
		||||
,null 'Referenced_Sln'
 | 
			
		||||
,null 'Referenced_Table'
 | 
			
		||||
from sys.foreign_key_columns a
 | 
			
		||||
inner join sys.tables b on b.object_id = a.parent_object_id
 | 
			
		||||
inner join sys.columns c on c.object_id = a.parent_object_id and c.column_id = a.parent_column_id
 | 
			
		||||
inner join sys.columns d on d.object_id = a.referenced_object_id and d.column_id = a.referenced_column_id
 | 
			
		||||
left join sys.foreign_keys e on e.object_id = a.constraint_object_id
 | 
			
		||||
where b.object_id in ({loc8})
 | 
			
		||||
;
 | 
			
		||||
use [{olddatabase}];
 | 
			
		||||
";
 | 
			
		||||
                ds = _orm.Ado.ExecuteArray(CommandType.Text, sql);
 | 
			
		||||
                if (ds == null) return loc1;
 | 
			
		||||
 | 
			
		||||
                var fkColumns = new Dictionary<int, Dictionary<string, DbForeignInfo>>();
 | 
			
		||||
                foreach (object[] row in ds)
 | 
			
		||||
                {
 | 
			
		||||
                    int object_id, referenced_object_id;
 | 
			
		||||
                    int.TryParse(string.Concat(row[0]), out object_id);
 | 
			
		||||
                    var column = string.Concat(row[1]);
 | 
			
		||||
                    string fk_id = string.Concat(row[2]);
 | 
			
		||||
                    int.TryParse(string.Concat(row[3]), out referenced_object_id);
 | 
			
		||||
                    var is_foreign_key = bool.Parse(string.Concat(row[4]));
 | 
			
		||||
                    var referenced_column = string.Concat(row[5]);
 | 
			
		||||
                    var referenced_db = string.Concat(row[6]);
 | 
			
		||||
                    var referenced_table = string.Concat(row[7]);
 | 
			
		||||
                    DbColumnInfo loc9 = loc3[object_id][column];
 | 
			
		||||
                    DbTableInfo loc10 = null;
 | 
			
		||||
                    DbColumnInfo loc11 = null;
 | 
			
		||||
                    bool isThisSln = referenced_object_id != 0;
 | 
			
		||||
 | 
			
		||||
                    if (isThisSln)
 | 
			
		||||
                    {
 | 
			
		||||
                        loc10 = loc2[referenced_object_id];
 | 
			
		||||
                        loc11 = loc3[referenced_object_id][referenced_column];
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
 | 
			
		||||
                    }
 | 
			
		||||
                    Dictionary<string, DbForeignInfo> loc12 = null;
 | 
			
		||||
                    DbForeignInfo loc13 = null;
 | 
			
		||||
                    if (!fkColumns.TryGetValue(object_id, out loc12))
 | 
			
		||||
                        fkColumns.Add(object_id, loc12 = new Dictionary<string, DbForeignInfo>());
 | 
			
		||||
                    if (!loc12.TryGetValue(fk_id, out loc13))
 | 
			
		||||
                        loc12.Add(fk_id, loc13 = new DbForeignInfo { Table = loc2[object_id], ReferencedTable = loc10 });
 | 
			
		||||
                    loc13.Columns.Add(loc9);
 | 
			
		||||
                    loc13.ReferencedColumns.Add(loc11);
 | 
			
		||||
                }
 | 
			
		||||
                foreach (var table_id in fkColumns.Keys)
 | 
			
		||||
                    foreach (var fk in fkColumns[table_id])
 | 
			
		||||
                        loc2[table_id].ForeignsDict.Add(fk.Key, fk.Value);
 | 
			
		||||
 | 
			
		||||
                foreach (var table_id in loc3.Keys)
 | 
			
		||||
                {
 | 
			
		||||
                    foreach (var loc5 in loc3[table_id].Values)
 | 
			
		||||
                    {
 | 
			
		||||
                        loc2[table_id].Columns.Add(loc5);
 | 
			
		||||
                        if (loc5.IsIdentity) loc2[table_id].Identitys.Add(loc5);
 | 
			
		||||
                        if (loc5.IsPrimary) loc2[table_id].Primarys.Add(loc5);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                foreach (var loc4 in loc2.Values)
 | 
			
		||||
                {
 | 
			
		||||
                    if (loc4.Primarys.Count == 0 && loc4.UniquesDict.Count > 0)
 | 
			
		||||
                    {
 | 
			
		||||
                        foreach (var loc5 in loc4.UniquesDict.First().Value)
 | 
			
		||||
                        {
 | 
			
		||||
                            loc5.IsPrimary = true;
 | 
			
		||||
                            loc4.Primarys.Add(loc5);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    loc4.Primarys.Sort((c1, c2) => c1.Name.CompareTo(c2.Name));
 | 
			
		||||
                    loc4.Columns.Sort((c1, c2) =>
 | 
			
		||||
                    {
 | 
			
		||||
                        int compare = c2.IsPrimary.CompareTo(c1.IsPrimary);
 | 
			
		||||
                        if (compare == 0)
 | 
			
		||||
                        {
 | 
			
		||||
                            bool b1 = loc4.ForeignsDict.Values.Where(fk => fk.Columns.Where(c3 => c3.Name == c1.Name).Any()).Any();
 | 
			
		||||
                            bool b2 = loc4.ForeignsDict.Values.Where(fk => fk.Columns.Where(c3 => c3.Name == c2.Name).Any()).Any();
 | 
			
		||||
                            compare = b2.CompareTo(b1);
 | 
			
		||||
                        }
 | 
			
		||||
                        if (compare == 0) compare = c1.Name.CompareTo(c2.Name);
 | 
			
		||||
                        return compare;
 | 
			
		||||
                    });
 | 
			
		||||
                    loc1.Add(loc4);
 | 
			
		||||
                }
 | 
			
		||||
                loc1.Sort((t1, t2) =>
 | 
			
		||||
                {
 | 
			
		||||
                    var ret = t1.Schema.CompareTo(t2.Schema);
 | 
			
		||||
                    if (ret == 0) ret = t1.Name.CompareTo(t2.Name);
 | 
			
		||||
                    return ret;
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                loc2.Clear();
 | 
			
		||||
                loc3.Clear();
 | 
			
		||||
                tables.AddRange(loc1);
 | 
			
		||||
            }
 | 
			
		||||
            return tables;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public List<DbEnumInfo> GetEnumsByDatabase(params string[] database)
 | 
			
		||||
        {
 | 
			
		||||
            return new List<DbEnumInfo>();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,438 @@
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using FreeSql.Internal.Model;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Linq.Expressions;
 | 
			
		||||
using System.Text;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.SqlServer
 | 
			
		||||
{
 | 
			
		||||
    class OdbcSqlServerExpression : CommonExpression
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        public OdbcSqlServerExpression(CommonUtils common) : base(common) { }
 | 
			
		||||
 | 
			
		||||
        public override string ExpressionLambdaToSqlOther(Expression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            Func<Expression, string> getExp = exparg => ExpressionLambdaToSql(exparg, tsc);
 | 
			
		||||
            switch (exp.NodeType)
 | 
			
		||||
            {
 | 
			
		||||
                case ExpressionType.Convert:
 | 
			
		||||
                    var operandExp = (exp as UnaryExpression)?.Operand;
 | 
			
		||||
                    var gentype = exp.Type.NullableTypeOrThis();
 | 
			
		||||
                    if (gentype != operandExp.Type.NullableTypeOrThis())
 | 
			
		||||
                    {
 | 
			
		||||
                        switch (gentype.ToString())
 | 
			
		||||
                        {
 | 
			
		||||
                            case "System.Boolean": return $"(cast({getExp(operandExp)} as varchar) not in ('0','false'))";
 | 
			
		||||
                            case "System.Byte": return $"cast({getExp(operandExp)} as tinyint)";
 | 
			
		||||
                            case "System.Char": return $"substring(cast({getExp(operandExp)} as nvarchar),1,1)";
 | 
			
		||||
                            case "System.DateTime": return $"cast({getExp(operandExp)} as datetime)";
 | 
			
		||||
                            case "System.Decimal": return $"cast({getExp(operandExp)} as decimal(36,18))";
 | 
			
		||||
                            case "System.Double": return $"cast({getExp(operandExp)} as decimal(32,16))";
 | 
			
		||||
                            case "System.Int16": return $"cast({getExp(operandExp)} as smallint)";
 | 
			
		||||
                            case "System.Int32": return $"cast({getExp(operandExp)} as int)";
 | 
			
		||||
                            case "System.Int64": return $"cast({getExp(operandExp)} as bigint)";
 | 
			
		||||
                            case "System.SByte": return $"cast({getExp(operandExp)} as tinyint)";
 | 
			
		||||
                            case "System.Single": return $"cast({getExp(operandExp)} as decimal(14,7))";
 | 
			
		||||
                            case "System.String": return operandExp.Type.NullableTypeOrThis() == typeof(Guid) ? $"cast({getExp(operandExp)} as varchar(36))" : $"cast({getExp(operandExp)} as nvarchar)";
 | 
			
		||||
                            case "System.UInt16": return $"cast({getExp(operandExp)} as smallint)";
 | 
			
		||||
                            case "System.UInt32": return $"cast({getExp(operandExp)} as int)";
 | 
			
		||||
                            case "System.UInt64": return $"cast({getExp(operandExp)} as bigint)";
 | 
			
		||||
                            case "System.Guid": return $"cast({getExp(operandExp)} as uniqueidentifier)";
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
                case ExpressionType.Call:
 | 
			
		||||
                    var callExp = exp as MethodCallExpression;
 | 
			
		||||
 | 
			
		||||
                    switch (callExp.Method.Name)
 | 
			
		||||
                    {
 | 
			
		||||
                        case "Parse":
 | 
			
		||||
                        case "TryParse":
 | 
			
		||||
                            switch (callExp.Method.DeclaringType.NullableTypeOrThis().ToString())
 | 
			
		||||
                            {
 | 
			
		||||
                                case "System.Boolean": return $"(cast({getExp(callExp.Arguments[0])} as varchar) not in ('0','false'))";
 | 
			
		||||
                                case "System.Byte": return $"cast({getExp(callExp.Arguments[0])} as tinyint)";
 | 
			
		||||
                                case "System.Char": return $"substring(cast({getExp(callExp.Arguments[0])} as nvarchar),1,1)";
 | 
			
		||||
                                case "System.DateTime": return $"cast({getExp(callExp.Arguments[0])} as datetime)";
 | 
			
		||||
                                case "System.Decimal": return $"cast({getExp(callExp.Arguments[0])} as decimal(36,18))";
 | 
			
		||||
                                case "System.Double": return $"cast({getExp(callExp.Arguments[0])} as decimal(32,16))";
 | 
			
		||||
                                case "System.Int16": return $"cast({getExp(callExp.Arguments[0])} as smallint)";
 | 
			
		||||
                                case "System.Int32": return $"cast({getExp(callExp.Arguments[0])} as int)";
 | 
			
		||||
                                case "System.Int64": return $"cast({getExp(callExp.Arguments[0])} as bigint)";
 | 
			
		||||
                                case "System.SByte": return $"cast({getExp(callExp.Arguments[0])} as tinyint)";
 | 
			
		||||
                                case "System.Single": return $"cast({getExp(callExp.Arguments[0])} as decimal(14,7))";
 | 
			
		||||
                                case "System.UInt16": return $"cast({getExp(callExp.Arguments[0])} as smallint)";
 | 
			
		||||
                                case "System.UInt32": return $"cast({getExp(callExp.Arguments[0])} as int)";
 | 
			
		||||
                                case "System.UInt64": return $"cast({getExp(callExp.Arguments[0])} as bigint)";
 | 
			
		||||
                                case "System.Guid": return $"cast({getExp(callExp.Arguments[0])} as uniqueidentifier)";
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "NewGuid":
 | 
			
		||||
                            switch (callExp.Method.DeclaringType.NullableTypeOrThis().ToString())
 | 
			
		||||
                            {
 | 
			
		||||
                                case "System.Guid": return $"newid()";
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "Next":
 | 
			
		||||
                            if (callExp.Object?.Type == typeof(Random)) return "cast(rand()*1000000000 as int)";
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "NextDouble":
 | 
			
		||||
                            if (callExp.Object?.Type == typeof(Random)) return "rand()";
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "Random":
 | 
			
		||||
                            if (callExp.Method.DeclaringType.IsNumberType()) return "rand()";
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "ToString":
 | 
			
		||||
                            if (callExp.Object != null) return callExp.Arguments.Count == 0 ? (callExp.Object.Type.NullableTypeOrThis() == typeof(Guid) ? $"cast({getExp(callExp.Object)} as varchar(36))" : $"cast({getExp(callExp.Object)} as nvarchar)") : null;
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var objExp = callExp.Object;
 | 
			
		||||
                    var objType = objExp?.Type;
 | 
			
		||||
                    if (objType?.FullName == "System.Byte[]") return null;
 | 
			
		||||
 | 
			
		||||
                    var argIndex = 0;
 | 
			
		||||
                    if (objType == null && callExp.Method.DeclaringType == typeof(Enumerable))
 | 
			
		||||
                    {
 | 
			
		||||
                        objExp = callExp.Arguments.FirstOrDefault();
 | 
			
		||||
                        objType = objExp?.Type;
 | 
			
		||||
                        argIndex++;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (objType == null) objType = callExp.Method.DeclaringType;
 | 
			
		||||
                    if (objType != null || objType.IsArray || typeof(IList).IsAssignableFrom(callExp.Method.DeclaringType))
 | 
			
		||||
                    {
 | 
			
		||||
                        var left = objExp == null ? null : getExp(objExp);
 | 
			
		||||
                        switch (callExp.Method.Name)
 | 
			
		||||
                        {
 | 
			
		||||
                            case "Contains":
 | 
			
		||||
                                //判断 in
 | 
			
		||||
                                return $"({getExp(callExp.Arguments[argIndex])}) in {left}";
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
                case ExpressionType.NewArrayInit:
 | 
			
		||||
                    var arrExp = exp as NewArrayExpression;
 | 
			
		||||
                    var arrSb = new StringBuilder();
 | 
			
		||||
                    arrSb.Append("(");
 | 
			
		||||
                    for (var a = 0; a < arrExp.Expressions.Count; a++)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (a > 0) arrSb.Append(",");
 | 
			
		||||
                        arrSb.Append(getExp(arrExp.Expressions[a]));
 | 
			
		||||
                    }
 | 
			
		||||
                    if (arrSb.Length == 1) arrSb.Append("NULL");
 | 
			
		||||
                    return arrSb.Append(")").ToString();
 | 
			
		||||
                case ExpressionType.ListInit:
 | 
			
		||||
                    var listExp = exp as ListInitExpression;
 | 
			
		||||
                    var listSb = new StringBuilder();
 | 
			
		||||
                    listSb.Append("(");
 | 
			
		||||
                    for (var a = 0; a < listExp.Initializers.Count; a++)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (listExp.Initializers[a].Arguments.Any() == false) continue;
 | 
			
		||||
                        if (a > 0) listSb.Append(",");
 | 
			
		||||
                        listSb.Append(getExp(listExp.Initializers[a].Arguments.FirstOrDefault()));
 | 
			
		||||
                    }
 | 
			
		||||
                    if (listSb.Length == 1) listSb.Append("NULL");
 | 
			
		||||
                    return listSb.Append(")").ToString();
 | 
			
		||||
                case ExpressionType.New:
 | 
			
		||||
                    var newExp = exp as NewExpression;
 | 
			
		||||
                    if (typeof(IList).IsAssignableFrom(newExp.Type))
 | 
			
		||||
                    {
 | 
			
		||||
                        if (newExp.Arguments.Count == 0) return "(NULL)";
 | 
			
		||||
                        if (typeof(IEnumerable).IsAssignableFrom(newExp.Arguments[0].Type) == false) return "(NULL)";
 | 
			
		||||
                        return getExp(newExp.Arguments[0]);
 | 
			
		||||
                    }
 | 
			
		||||
                    return null;
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override string ExpressionLambdaToSqlMemberAccessString(MemberExpression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            if (exp.Expression == null)
 | 
			
		||||
            {
 | 
			
		||||
                switch (exp.Member.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "Empty": return "''";
 | 
			
		||||
                }
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            var left = ExpressionLambdaToSql(exp.Expression, tsc);
 | 
			
		||||
            switch (exp.Member.Name)
 | 
			
		||||
            {
 | 
			
		||||
                case "Length": return $"len({left})";
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        public override string ExpressionLambdaToSqlMemberAccessDateTime(MemberExpression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            if (exp.Expression == null)
 | 
			
		||||
            {
 | 
			
		||||
                switch (exp.Member.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "Now": return "getdate()";
 | 
			
		||||
                    case "UtcNow": return "getutcdate()";
 | 
			
		||||
                    case "Today": return "convert(char(10),getdate(),120)";
 | 
			
		||||
                    case "MinValue": return "'1753/1/1 0:00:00'";
 | 
			
		||||
                    case "MaxValue": return "'9999/12/31 23:59:59'";
 | 
			
		||||
                }
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            var left = ExpressionLambdaToSql(exp.Expression, tsc);
 | 
			
		||||
            switch (exp.Member.Name)
 | 
			
		||||
            {
 | 
			
		||||
                case "Date": return $"convert(char(10),{left},120)";
 | 
			
		||||
                case "TimeOfDay": return $"datediff(second, convert(char(10),{left},120), {left})";
 | 
			
		||||
                case "DayOfWeek": return $"(datepart(weekday, {left})-1)";
 | 
			
		||||
                case "Day": return $"datepart(day, {left})";
 | 
			
		||||
                case "DayOfYear": return $"datepart(dayofyear, {left})";
 | 
			
		||||
                case "Month": return $"datepart(month, {left})";
 | 
			
		||||
                case "Year": return $"datepart(year, {left})";
 | 
			
		||||
                case "Hour": return $"datepart(hour, {left})";
 | 
			
		||||
                case "Minute": return $"datepart(minute, {left})";
 | 
			
		||||
                case "Second": return $"datepart(second, {left})";
 | 
			
		||||
                case "Millisecond": return $"(datepart(millisecond, {left})/1000)";
 | 
			
		||||
                case "Ticks": return $"(cast(datediff(second, '1970-1-1', {left}) as bigint)*10000000+621355968000000000)";
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        public override string ExpressionLambdaToSqlMemberAccessTimeSpan(MemberExpression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            if (exp.Expression == null)
 | 
			
		||||
            {
 | 
			
		||||
                switch (exp.Member.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "Zero": return "0";
 | 
			
		||||
                    case "MinValue": return "-922337203685477580"; //微秒 Ticks / 10
 | 
			
		||||
                    case "MaxValue": return "922337203685477580";
 | 
			
		||||
                }
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            var left = ExpressionLambdaToSql(exp.Expression, tsc);
 | 
			
		||||
            switch (exp.Member.Name)
 | 
			
		||||
            {
 | 
			
		||||
                case "Days": return $"floor(({left})/{60 * 60 * 24})";
 | 
			
		||||
                case "Hours": return $"floor(({left})/{60 * 60}%24)";
 | 
			
		||||
                case "Milliseconds": return $"(cast({left} as bigint)*1000)";
 | 
			
		||||
                case "Minutes": return $"floor(({left})/60%60)";
 | 
			
		||||
                case "Seconds": return $"(({left})%60)";
 | 
			
		||||
                case "Ticks": return $"(cast({left} as bigint)*10000000)";
 | 
			
		||||
                case "TotalDays": return $"(({left})/{60 * 60 * 24})";
 | 
			
		||||
                case "TotalHours": return $"(({left})/{60 * 60})";
 | 
			
		||||
                case "TotalMilliseconds": return $"(cast({left} as bigint)*1000)";
 | 
			
		||||
                case "TotalMinutes": return $"(({left})/60)";
 | 
			
		||||
                case "TotalSeconds": return $"({left})";
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override string ExpressionLambdaToSqlCallString(MethodCallExpression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            Func<Expression, string> getExp = exparg => ExpressionLambdaToSql(exparg, tsc);
 | 
			
		||||
            if (exp.Object == null)
 | 
			
		||||
            {
 | 
			
		||||
                switch (exp.Method.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "IsNullOrEmpty":
 | 
			
		||||
                        var arg1 = getExp(exp.Arguments[0]);
 | 
			
		||||
                        return $"({arg1} is null or {arg1} = '')";
 | 
			
		||||
                    case "IsNullOrWhiteSpace":
 | 
			
		||||
                        var arg2 = getExp(exp.Arguments[0]);
 | 
			
		||||
                        return $"({arg2} is null or {arg2} = '' or ltrim({arg2}) = '')";
 | 
			
		||||
                    case "Concat":
 | 
			
		||||
                        return _common.StringConcat(exp.Arguments.Select(a => getExp(a)).ToArray(), exp.Arguments.Select(a => a.Type).ToArray());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                var left = getExp(exp.Object);
 | 
			
		||||
                switch (exp.Method.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "StartsWith":
 | 
			
		||||
                    case "EndsWith":
 | 
			
		||||
                    case "Contains":
 | 
			
		||||
                        var args0Value = getExp(exp.Arguments[0]);
 | 
			
		||||
                        if (args0Value == "NULL") return $"({left}) IS NULL";
 | 
			
		||||
                        if (exp.Method.Name == "StartsWith") return $"({left}) LIKE {(args0Value.EndsWith("'") ? args0Value.Insert(args0Value.Length - 1, "%") : $"(cast({args0Value} as nvarchar)+'%')")}";
 | 
			
		||||
                        if (exp.Method.Name == "EndsWith") return $"({left}) LIKE {(args0Value.StartsWith("'") ? args0Value.Insert(1, "%") : $"('%'+cast({args0Value} as nvarchar))")}";
 | 
			
		||||
                        if (args0Value.StartsWith("'") && args0Value.EndsWith("'")) return $"({left}) LIKE {args0Value.Insert(1, "%").Insert(args0Value.Length, "%")}";
 | 
			
		||||
                        return $"({left}) LIKE ('%'+cast({args0Value} as nvarchar)+'%')";
 | 
			
		||||
                    case "ToLower": return $"lower({left})";
 | 
			
		||||
                    case "ToUpper": return $"upper({left})";
 | 
			
		||||
                    case "Substring":
 | 
			
		||||
                        var substrArgs1 = getExp(exp.Arguments[0]);
 | 
			
		||||
                        if (long.TryParse(substrArgs1, out var testtrylng1)) substrArgs1 = (testtrylng1 + 1).ToString();
 | 
			
		||||
                        else substrArgs1 += "+1";
 | 
			
		||||
                        if (exp.Arguments.Count == 1) return $"left({left}, {substrArgs1})";
 | 
			
		||||
                        return $"substring({left}, {substrArgs1}, {getExp(exp.Arguments[1])})";
 | 
			
		||||
                    case "IndexOf":
 | 
			
		||||
                        var indexOfFindStr = getExp(exp.Arguments[0]);
 | 
			
		||||
                        if (exp.Arguments.Count > 1 && exp.Arguments[1].Type.FullName == "System.Int32")
 | 
			
		||||
                        {
 | 
			
		||||
                            var locateArgs1 = getExp(exp.Arguments[1]);
 | 
			
		||||
                            if (long.TryParse(locateArgs1, out var testtrylng2)) locateArgs1 = (testtrylng2 + 1).ToString();
 | 
			
		||||
                            else locateArgs1 += "+1";
 | 
			
		||||
                            return $"(charindex({left}, {indexOfFindStr}, {locateArgs1})-1)";
 | 
			
		||||
                        }
 | 
			
		||||
                        return $"(charindex({left}, {indexOfFindStr})-1)";
 | 
			
		||||
                    case "PadLeft":
 | 
			
		||||
                        if (exp.Arguments.Count == 1) return $"lpad({left}, {getExp(exp.Arguments[0])})";
 | 
			
		||||
                        return $"lpad({left}, {getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})";
 | 
			
		||||
                    case "PadRight":
 | 
			
		||||
                        if (exp.Arguments.Count == 1) return $"rpad({left}, {getExp(exp.Arguments[0])})";
 | 
			
		||||
                        return $"rpad({left}, {getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})";
 | 
			
		||||
                    case "Trim": return $"ltrim(rtrim({left}))";
 | 
			
		||||
                    case "TrimStart": return $"ltrim({left})";
 | 
			
		||||
                    case "TrimEnd": return $"rtrim({left})";
 | 
			
		||||
                    case "Replace": return $"replace({left}, {getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})";
 | 
			
		||||
                    case "CompareTo": return $"({left} - {getExp(exp.Arguments[0])})";
 | 
			
		||||
                    case "Equals": return $"({left} = {getExp(exp.Arguments[0])})";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        public override string ExpressionLambdaToSqlCallMath(MethodCallExpression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            Func<Expression, string> getExp = exparg => ExpressionLambdaToSql(exparg, tsc);
 | 
			
		||||
            switch (exp.Method.Name)
 | 
			
		||||
            {
 | 
			
		||||
                case "Abs": return $"abs({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Sign": return $"sign({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Floor": return $"floor({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Ceiling": return $"ceiling({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Round":
 | 
			
		||||
                    if (exp.Arguments.Count > 1 && exp.Arguments[1].Type.FullName == "System.Int32") return $"round({getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})";
 | 
			
		||||
                    return $"round({getExp(exp.Arguments[0])}, 0)";
 | 
			
		||||
                case "Exp": return $"exp({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Log": return $"log({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Log10": return $"log10({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Pow": return $"power({getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})";
 | 
			
		||||
                case "Sqrt": return $"sqrt({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Cos": return $"cos({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Sin": return $"sin({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Tan": return $"tan({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Acos": return $"acos({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Asin": return $"asin({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Atan": return $"atan({getExp(exp.Arguments[0])})";
 | 
			
		||||
                case "Atan2": return $"atan2({getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})";
 | 
			
		||||
                case "Truncate": return $"floor({getExp(exp.Arguments[0])})";
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        public override string ExpressionLambdaToSqlCallDateTime(MethodCallExpression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            Func<Expression, string> getExp = exparg => ExpressionLambdaToSql(exparg, tsc);
 | 
			
		||||
            if (exp.Object == null)
 | 
			
		||||
            {
 | 
			
		||||
                switch (exp.Method.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "Compare": return $"({getExp(exp.Arguments[0])} - ({getExp(exp.Arguments[1])}))";
 | 
			
		||||
                    case "DaysInMonth": return $"datepart(day, dateadd(day, -1, dateadd(month, 1, cast({getExp(exp.Arguments[0])} as varchar) + '-' + cast({getExp(exp.Arguments[1])} as varchar) + '-1')))";
 | 
			
		||||
                    case "Equals": return $"({getExp(exp.Arguments[0])} = {getExp(exp.Arguments[1])})";
 | 
			
		||||
 | 
			
		||||
                    case "IsLeapYear":
 | 
			
		||||
                        var isLeapYearArgs1 = getExp(exp.Arguments[0]);
 | 
			
		||||
                        return $"(({isLeapYearArgs1})%4=0 AND ({isLeapYearArgs1})%100<>0 OR ({isLeapYearArgs1})%400=0)";
 | 
			
		||||
 | 
			
		||||
                    case "Parse": return $"cast({getExp(exp.Arguments[0])} as datetime)";
 | 
			
		||||
                    case "ParseExact":
 | 
			
		||||
                    case "TryParse":
 | 
			
		||||
                    case "TryParseExact": return $"cast({getExp(exp.Arguments[0])} as datetime)";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                var left = getExp(exp.Object);
 | 
			
		||||
                var args1 = exp.Arguments.Count == 0 ? null : getExp(exp.Arguments[0]);
 | 
			
		||||
                switch (exp.Method.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "Add": return $"dateadd(second, {args1}, {left})";
 | 
			
		||||
                    case "AddDays": return $"dateadd(day, {args1}, {left})";
 | 
			
		||||
                    case "AddHours": return $"dateadd(hour, {args1}, {left})";
 | 
			
		||||
                    case "AddMilliseconds": return $"dateadd(second, ({args1})/1000, {left})";
 | 
			
		||||
                    case "AddMinutes": return $"dateadd(minute, {args1}, {left})";
 | 
			
		||||
                    case "AddMonths": return $"dateadd(month, {args1}, {left})";
 | 
			
		||||
                    case "AddSeconds": return $"dateadd(second, {args1}, {left})";
 | 
			
		||||
                    case "AddTicks": return $"dateadd(second, ({args1})/10000000, {left})";
 | 
			
		||||
                    case "AddYears": return $"dateadd(year, {args1}, {left})";
 | 
			
		||||
                    case "Subtract":
 | 
			
		||||
                        switch ((exp.Arguments[0].Type.IsNullableType() ? exp.Arguments[0].Type.GenericTypeArguments.FirstOrDefault() : exp.Arguments[0].Type).FullName)
 | 
			
		||||
                        {
 | 
			
		||||
                            case "System.DateTime": return $"datediff(second, {args1}, {left})";
 | 
			
		||||
                            case "System.TimeSpan": return $"dateadd(second, {args1}*-1, {left})";
 | 
			
		||||
                        }
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "Equals": return $"({left} = {getExp(exp.Arguments[0])})";
 | 
			
		||||
                    case "CompareTo": return $"datediff(second,{getExp(exp.Arguments[0])},{left})";
 | 
			
		||||
                    case "ToString": return exp.Arguments.Count == 0 ? $"convert(varchar, {left}, 121)" : null;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        public override string ExpressionLambdaToSqlCallTimeSpan(MethodCallExpression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            Func<Expression, string> getExp = exparg => ExpressionLambdaToSql(exparg, tsc);
 | 
			
		||||
            if (exp.Object == null)
 | 
			
		||||
            {
 | 
			
		||||
                switch (exp.Method.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "Compare": return $"({getExp(exp.Arguments[0])}-({getExp(exp.Arguments[1])}))";
 | 
			
		||||
                    case "Equals": return $"({getExp(exp.Arguments[0])} = {getExp(exp.Arguments[1])})";
 | 
			
		||||
                    case "FromDays": return $"(({getExp(exp.Arguments[0])})*{60 * 60 * 24})";
 | 
			
		||||
                    case "FromHours": return $"(({getExp(exp.Arguments[0])})*{60 * 60})";
 | 
			
		||||
                    case "FromMilliseconds": return $"(({getExp(exp.Arguments[0])})/1000)";
 | 
			
		||||
                    case "FromMinutes": return $"(({getExp(exp.Arguments[0])})*60)";
 | 
			
		||||
                    case "FromSeconds": return $"({getExp(exp.Arguments[0])})";
 | 
			
		||||
                    case "FromTicks": return $"(({getExp(exp.Arguments[0])})/10000000)";
 | 
			
		||||
                    case "Parse": return $"cast({getExp(exp.Arguments[0])} as bigint)";
 | 
			
		||||
                    case "ParseExact":
 | 
			
		||||
                    case "TryParse":
 | 
			
		||||
                    case "TryParseExact": return $"cast({getExp(exp.Arguments[0])} as bigint)";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                var left = getExp(exp.Object);
 | 
			
		||||
                var args1 = exp.Arguments.Count == 0 ? null : getExp(exp.Arguments[0]);
 | 
			
		||||
                switch (exp.Method.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "Add": return $"({left}+{args1})";
 | 
			
		||||
                    case "Subtract": return $"({left}-({args1}))";
 | 
			
		||||
                    case "Equals": return $"({left} = {getExp(exp.Arguments[0])})";
 | 
			
		||||
                    case "CompareTo": return $"({left}-({getExp(exp.Arguments[0])}))";
 | 
			
		||||
                    case "ToString": return $"cast({left} as varchar)";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        public override string ExpressionLambdaToSqlCallConvert(MethodCallExpression exp, ExpTSC tsc)
 | 
			
		||||
        {
 | 
			
		||||
            Func<Expression, string> getExp = exparg => ExpressionLambdaToSql(exparg, tsc);
 | 
			
		||||
            if (exp.Object == null)
 | 
			
		||||
            {
 | 
			
		||||
                switch (exp.Method.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    case "ToBoolean": return $"(cast({getExp(exp.Arguments[0])} as varchar) not in ('0','false'))";
 | 
			
		||||
                    case "ToByte": return $"cast({getExp(exp.Arguments[0])} as tinyint)";
 | 
			
		||||
                    case "ToChar": return $"substring(cast({getExp(exp.Arguments[0])} as nvarchar),1,1)";
 | 
			
		||||
                    case "ToDateTime": return $"cast({getExp(exp.Arguments[0])} as datetime)";
 | 
			
		||||
                    case "ToDecimal": return $"cast({getExp(exp.Arguments[0])} as decimal(36,18))";
 | 
			
		||||
                    case "ToDouble": return $"cast({getExp(exp.Arguments[0])} as decimal(32,16))";
 | 
			
		||||
                    case "ToInt16": return $"cast({getExp(exp.Arguments[0])} as smallint)";
 | 
			
		||||
                    case "ToInt32": return $"cast({getExp(exp.Arguments[0])} as int)";
 | 
			
		||||
                    case "ToInt64": return $"cast({getExp(exp.Arguments[0])} as bigint)";
 | 
			
		||||
                    case "ToSByte": return $"cast({getExp(exp.Arguments[0])} as tinyint)";
 | 
			
		||||
                    case "ToSingle": return $"cast({getExp(exp.Arguments[0])} as decimal(14,7))";
 | 
			
		||||
                    case "ToString": return exp.Arguments[0].Type.NullableTypeOrThis() == typeof(Guid) ? $"cast({getExp(exp.Arguments[0])} as varchar(36))" : $"cast({getExp(exp.Arguments[0])} as nvarchar)";
 | 
			
		||||
                    case "ToUInt16": return $"cast({getExp(exp.Arguments[0])} as smallint)";
 | 
			
		||||
                    case "ToUInt32": return $"cast({getExp(exp.Arguments[0])} as int)";
 | 
			
		||||
                    case "ToUInt64": return $"cast({getExp(exp.Arguments[0])} as bigint)";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,69 @@
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using FreeSql.Internal.CommonProvider;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.SqlServer
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    public class OdbcSqlServerProvider<TMark> : IFreeSql<TMark>
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        public ISelect<T1> Select<T1>() where T1 : class => new OdbcSqlServerSelect<T1>(this, this.InternalCommonUtils, this.InternalCommonExpression, null);
 | 
			
		||||
        public ISelect<T1> Select<T1>(object dywhere) where T1 : class => new OdbcSqlServerSelect<T1>(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere);
 | 
			
		||||
        public IInsert<T1> Insert<T1>() where T1 : class => new OdbcSqlServerInsert<T1>(this, this.InternalCommonUtils, this.InternalCommonExpression);
 | 
			
		||||
        public IInsert<T1> Insert<T1>(T1 source) where T1 : class => this.Insert<T1>().AppendData(source);
 | 
			
		||||
        public IInsert<T1> Insert<T1>(T1[] source) where T1 : class => this.Insert<T1>().AppendData(source);
 | 
			
		||||
        public IInsert<T1> Insert<T1>(IEnumerable<T1> source) where T1 : class => this.Insert<T1>().AppendData(source);
 | 
			
		||||
        public IUpdate<T1> Update<T1>() where T1 : class => new OdbcSqlServerUpdate<T1>(this, this.InternalCommonUtils, this.InternalCommonExpression, null);
 | 
			
		||||
        public IUpdate<T1> Update<T1>(object dywhere) where T1 : class => new OdbcSqlServerUpdate<T1>(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere);
 | 
			
		||||
        public IDelete<T1> Delete<T1>() where T1 : class => new OdbcSqlServerDelete<T1>(this, this.InternalCommonUtils, this.InternalCommonExpression, null);
 | 
			
		||||
        public IDelete<T1> Delete<T1>(object dywhere) where T1 : class => new OdbcSqlServerDelete<T1>(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere);
 | 
			
		||||
 | 
			
		||||
        public IAdo Ado { get; }
 | 
			
		||||
        public IAop Aop { get; }
 | 
			
		||||
        public ICodeFirst CodeFirst { get; }
 | 
			
		||||
        public IDbFirst DbFirst { get; }
 | 
			
		||||
        public OdbcSqlServerProvider(string masterConnectionString, string[] slaveConnectionString)
 | 
			
		||||
        {
 | 
			
		||||
            this.InternalCommonUtils = new OdbcSqlServerUtils(this);
 | 
			
		||||
            this.InternalCommonExpression = new OdbcSqlServerExpression(this.InternalCommonUtils);
 | 
			
		||||
 | 
			
		||||
            this.Ado = new OdbcSqlServerAdo(this.InternalCommonUtils, masterConnectionString, slaveConnectionString);
 | 
			
		||||
            this.Aop = new AopProvider();
 | 
			
		||||
 | 
			
		||||
            this.DbFirst = new OdbcSqlServerDbFirst(this, this.InternalCommonUtils, this.InternalCommonExpression);
 | 
			
		||||
            this.CodeFirst = new OdbcSqlServerCodeFirst(this, this.InternalCommonUtils, this.InternalCommonExpression);
 | 
			
		||||
 | 
			
		||||
            if (this.Ado.MasterPool != null)
 | 
			
		||||
                using (var conn = this.Ado.MasterPool.Get())
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        (this.InternalCommonUtils as OdbcSqlServerUtils).IsSelectRowNumber = int.Parse(conn.Value.ServerVersion.Split('.')[0]) <= 10;
 | 
			
		||||
                    }
 | 
			
		||||
                    catch
 | 
			
		||||
                    {
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        internal CommonUtils InternalCommonUtils { get; }
 | 
			
		||||
        internal CommonExpression InternalCommonExpression { get; }
 | 
			
		||||
 | 
			
		||||
        public void Transaction(Action handler) => Ado.Transaction(handler);
 | 
			
		||||
 | 
			
		||||
        public void Transaction(Action handler, TimeSpan timeout) => Ado.Transaction(handler, timeout);
 | 
			
		||||
 | 
			
		||||
        ~OdbcSqlServerProvider()
 | 
			
		||||
        {
 | 
			
		||||
            this.Dispose();
 | 
			
		||||
        }
 | 
			
		||||
        bool _isdisposed = false;
 | 
			
		||||
        public void Dispose()
 | 
			
		||||
        {
 | 
			
		||||
            if (_isdisposed) return;
 | 
			
		||||
            (this.Ado as AdoProvider)?.Dispose();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,96 @@
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data;
 | 
			
		||||
using System.Data.Common;
 | 
			
		||||
using System.Data.Odbc;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Linq.Expressions;
 | 
			
		||||
using System.Text;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Odbc.SqlServer
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    public class OdbcSqlServerUtils : CommonUtils
 | 
			
		||||
    {
 | 
			
		||||
        public OdbcSqlServerUtils(IFreeSql orm) : base(orm)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool IsSelectRowNumber = true;
 | 
			
		||||
 | 
			
		||||
        public override DbParameter AppendParamter(List<DbParameter> _params, string parameterName, Type type, object value)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrEmpty(parameterName)) parameterName = $"p_{_params?.Count}";
 | 
			
		||||
            if (value?.Equals(DateTime.MinValue) == true) value = new DateTime(1970, 1, 1);
 | 
			
		||||
            var ret = new OdbcParameter { ParameterName = QuoteParamterName(parameterName), Value = value };
 | 
			
		||||
            var tp = _orm.CodeFirst.GetDbInfo(type)?.type;
 | 
			
		||||
            if (tp != null) ret.OdbcType = (OdbcType)tp.Value;
 | 
			
		||||
            _params?.Add(ret);
 | 
			
		||||
            return ret;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override DbParameter[] GetDbParamtersByObject(string sql, object obj) =>
 | 
			
		||||
            Utils.GetDbParamtersByObject<OdbcParameter>(sql, obj, null, (name, type, value) =>
 | 
			
		||||
            {
 | 
			
		||||
                if (value?.Equals(DateTime.MinValue) == true) value = new DateTime(1970, 1, 1);
 | 
			
		||||
                var ret = new OdbcParameter { ParameterName = $"@{name}", Value = value };
 | 
			
		||||
                var tp = _orm.CodeFirst.GetDbInfo(type)?.type;
 | 
			
		||||
                if (tp != null) ret.OdbcType = (OdbcType)tp.Value;
 | 
			
		||||
                return ret;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        public override string FormatSql(string sql, params object[] args) => sql?.FormatOdbcSqlServer(args);
 | 
			
		||||
        public override string QuoteSqlName(string name)
 | 
			
		||||
        {
 | 
			
		||||
            var nametrim = name.Trim();
 | 
			
		||||
            if (nametrim.StartsWith("(") && nametrim.EndsWith(")"))
 | 
			
		||||
                return nametrim; //原生SQL
 | 
			
		||||
            return $"[{nametrim.TrimStart('[').TrimEnd(']').Replace(".", "].[")}]";
 | 
			
		||||
        }
 | 
			
		||||
        public override string TrimQuoteSqlName(string name)
 | 
			
		||||
        {
 | 
			
		||||
            var nametrim = name.Trim();
 | 
			
		||||
            if (nametrim.StartsWith("(") && nametrim.EndsWith(")"))
 | 
			
		||||
                return nametrim; //原生SQL
 | 
			
		||||
            return $"{nametrim.TrimStart('[').TrimEnd(']').Replace("].[", ".").Replace(".[", ".")}";
 | 
			
		||||
        }
 | 
			
		||||
        public override string QuoteParamterName(string name) => $"@{(_orm.CodeFirst.IsSyncStructureToLower ? name.ToLower() : name)}";
 | 
			
		||||
        public override string IsNull(string sql, object value) => $"isnull({sql}, {value})";
 | 
			
		||||
        public override string StringConcat(string[] objs, Type[] types)
 | 
			
		||||
        {
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            var news = new string[objs.Length];
 | 
			
		||||
            for (var a = 0; a < objs.Length; a++)
 | 
			
		||||
                news[a] = types[a] == typeof(string) ? objs[a] : $"cast({objs[a]} as nvarchar)";
 | 
			
		||||
            return string.Join(" + ", news);
 | 
			
		||||
        }
 | 
			
		||||
        public override string Mod(string left, string right, Type leftType, Type rightType) => $"{left} % {right}";
 | 
			
		||||
        public override string Div(string left, string right, Type leftType, Type rightType) => $"{left} / {right}";
 | 
			
		||||
 | 
			
		||||
        public override string QuoteWriteParamter(Type type, string paramterName) => paramterName;
 | 
			
		||||
        public override string QuoteReadColumn(Type type, string columnName) => columnName;
 | 
			
		||||
 | 
			
		||||
        public override string GetNoneParamaterSqlValue(List<DbParameter> specialParams, Type type, object value)
 | 
			
		||||
        {
 | 
			
		||||
            if (value == null) return "NULL";
 | 
			
		||||
            if (type == typeof(byte[]))
 | 
			
		||||
            {
 | 
			
		||||
                var bytes = value as byte[];
 | 
			
		||||
                var sb = new StringBuilder().Append("0x");
 | 
			
		||||
                foreach (var vc in bytes)
 | 
			
		||||
                {
 | 
			
		||||
                    if (vc < 10) sb.Append("0");
 | 
			
		||||
                    sb.Append(vc.ToString("X"));
 | 
			
		||||
                }
 | 
			
		||||
                return sb.ToString();
 | 
			
		||||
            }
 | 
			
		||||
            else if (type == typeof(TimeSpan) || type == typeof(TimeSpan?))
 | 
			
		||||
            {
 | 
			
		||||
                var ts = (TimeSpan)value;
 | 
			
		||||
                value = $"{ts.Hours}:{ts.Minutes}:{ts.Seconds}.{ts.Milliseconds}";
 | 
			
		||||
            }
 | 
			
		||||
            return FormatSql("{0}", value, 1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user