2024-11-13 18:18:28 +08:00

482 lines
29 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using FreeSql.Internal;
using FreeSql.Internal.Model;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.OleDb;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace FreeSql.MsAccess
{
class MsAccessCodeFirst : Internal.CommonProvider.CodeFirstProvider
{
public override bool IsNoneCommandParameter { get => true; set => base.IsNoneCommandParameter = true; }
public MsAccessCodeFirst(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) : base(orm, commonUtils, commonExpression) { }
static object _dicCsToDbLock = new object();
static Dictionary<string, CsToDb<OleDbType>> _dicCsToDb = new Dictionary<string, CsToDb<OleDbType>>() {
{ typeof(bool).FullName, CsToDb.New(OleDbType.Boolean, "bit","bit NOT NULL", null, false, false) },{ typeof(bool?).FullName, CsToDb.New(OleDbType.Boolean, "bit","bit", null, true, null) },
{ typeof(sbyte).FullName, CsToDb.New(OleDbType.TinyInt, "decimal", "decimal(3,0) NOT NULL", false, false, 0) },{ typeof(sbyte?).FullName, CsToDb.New(OleDbType.TinyInt, "decimal", "decimal(3,0)", false, true, null) },
{ typeof(short).FullName, CsToDb.New(OleDbType.SmallInt, "decimal","decimal(6,0) NOT NULL", false, false, 0) },{ typeof(short?).FullName, CsToDb.New(OleDbType.SmallInt, "decimal", "decimal(6,0)", false, true, null) },
{ typeof(int).FullName, CsToDb.New(OleDbType.Integer, "decimal", "decimal(11,0) NOT NULL", false, false, 0) },{ typeof(int?).FullName, CsToDb.New(OleDbType.Integer, "decimal", "decimal(11,0)", false, true, null) },
{ typeof(long).FullName, CsToDb.New(OleDbType.BigInt, "decimal","decimal(20,0) NOT NULL", false, false, 0) },{ typeof(long?).FullName, CsToDb.New(OleDbType.BigInt, "decimal","decimal(20,0)", false, true, null) },
// access int long 类型是留给自动增长用的,所以这里全映射为 decimal
{ typeof(byte).FullName, CsToDb.New(OleDbType.UnsignedTinyInt, "decimal","decimal(3,0) NOT NULL", true, false, 0) },{ typeof(byte?).FullName, CsToDb.New(OleDbType.UnsignedTinyInt, "decimal","decimal(3,0)", true, true, null) },
{ typeof(ushort).FullName, CsToDb.New(OleDbType.UnsignedSmallInt, "decimal","decimal(5,0) NOT NULL", true, false, 0) },{ typeof(ushort?).FullName, CsToDb.New(OleDbType.UnsignedSmallInt, "decimal", "decimal(5,0)", true, true, null) },
{ typeof(uint).FullName, CsToDb.New(OleDbType.UnsignedInt, "decimal", "decimal(10,0) NOT NULL", true, false, 0) },{ typeof(uint?).FullName, CsToDb.New(OleDbType.UnsignedInt, "decimal", "decimal(10,0)", true, true, null) },
{ typeof(ulong).FullName, CsToDb.New (OleDbType.UnsignedBigInt, "decimal", "decimal(20,0) NOT NULL", true, false, 0) },{ typeof(ulong?).FullName, CsToDb.New(OleDbType.UnsignedBigInt, "decimal", "decimal(20,0)", true, true, null) },
{ typeof(double).FullName, CsToDb.New(OleDbType.Double, "double", "double NOT NULL", false, false, 0) },{ typeof(double?).FullName, CsToDb.New(OleDbType.Double, "double", "double", false, true, null) },
{ typeof(float).FullName, CsToDb.New(OleDbType.Currency, "single","single NOT NULL", false, false, 0) },{ typeof(float?).FullName, CsToDb.New(OleDbType.Currency, "single","single", false, true, null) },
{ typeof(decimal).FullName, CsToDb.New(OleDbType.Decimal, "decimal", "decimal(10,2) NOT NULL", false, false, 0) },{ typeof(decimal?).FullName, CsToDb.New(OleDbType.Decimal, "decimal", "decimal(10,2)", false, true, null) },
{ typeof(TimeSpan).FullName, CsToDb.New(OleDbType.DBTime, "datetime","datetime NOT NULL", false, false, 0) },{ typeof(TimeSpan?).FullName, CsToDb.New(OleDbType.DBTime, "datetime", "datetime",false, true, null) },
{ typeof(DateTime).FullName, CsToDb.New(OleDbType.DBTimeStamp, "datetime", "datetime NOT NULL", false, false, new DateTime(1970,1,1)) },{ typeof(DateTime?).FullName, CsToDb.New(OleDbType.DBTimeStamp, "datetime", "datetime", false, true, null) },
{ typeof(byte[]).FullName, CsToDb.New(OleDbType.VarBinary, "varbinary", "varbinary(255)", false, null, new byte[0]) },
{ typeof(string).FullName, CsToDb.New(OleDbType.VarChar, "varchar", "varchar(255)", false, null, "") },
{ typeof(char).FullName, CsToDb.New(OleDbType.VarChar, "varchar", "varchar(1)", false, null, '\0') },
{ typeof(Guid).FullName, CsToDb.New(OleDbType.Guid, "varchar", "varchar(36) NOT NULL", false, false, Guid.Empty) },{ typeof(Guid?).FullName, CsToDb.New(OleDbType.Guid, "varchar", "varchar(36)", false, true, null) },
};
public override DbInfoResult GetDbInfo(Type type)
{
if (_dicCsToDb.TryGetValue(type.FullName, out var trydc)) return new DbInfoResult((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())
{
var genericTypes = type.GetGenericArguments();
if (genericTypes.Length == 1 && genericTypes.First().IsEnum) enumType = genericTypes.First();
}
if (enumType != null)
{
var newItem = enumType.GetCustomAttributes(typeof(FlagsAttribute), false).Any() ?
CsToDb.New(OleDbType.BigInt, "decimal", $"decimal(20,0){(type.IsEnum ? " NOT NULL" : "")}", false, type.IsEnum ? false : true, enumType.CreateInstanceGetDefaultValue()) :
CsToDb.New(OleDbType.Integer, "decimal", $"decimal(11,0){(type.IsEnum ? " NOT NULL" : "")}", false, type.IsEnum ? false : true, enumType.CreateInstanceGetDefaultValue());
if (_dicCsToDb.ContainsKey(type.FullName) == false)
{
lock (_dicCsToDbLock)
{
if (_dicCsToDb.ContainsKey(type.FullName) == false)
_dicCsToDb.Add(type.FullName, newItem);
}
}
return new DbInfoResult((int)newItem.type, newItem.dbtype, newItem.dbtypeFull, newItem.isnullable, newItem.defaultValue);
}
return null;
}
protected override string GetComparisonDDLStatements(params TypeSchemaAndName[] objects)
{
var sb = new StringBuilder();
var sbDeclare = new StringBuilder();
foreach (var obj in objects)
{
if (sb.Length > 0) sb.Append("\r\n");
var tb = obj.tableSchema;
if (tb == null) throw new Exception(CoreStrings.S_Type_IsNot_Migrable(obj.tableSchema.Type.FullName));
if (tb.Columns.Any() == false) throw new Exception(CoreStrings.S_Type_IsNot_Migrable_0Attributes(obj.tableSchema.Type.FullName));
var tbname = tb.DbName;
var tboldname = tb.DbOldName; //旧表名
if (string.Compare(tbname, tboldname, true) == 0) tboldname = null;
if (string.IsNullOrEmpty(obj.tableName) == false)
{
tbname = obj.tableName;
tboldname = null;
}
var sbalter = new StringBuilder();
var istmpatler = false; //创建临时表,导入数据,删除旧表,修改
var isexistsTb = false;
DataTable schemaTables = null;
using (var conn = _orm.Ado.MasterPool.Get())
{
schemaTables = conn.Value.GetSchema("Tables");
}
var schemaTablesTableNameIndex = 2;
for (var idx = 0; idx < schemaTables.Columns.Count; idx++)
if (string.Compare(schemaTables.Columns[idx].ColumnName, "TABLE_NAME", true) == 0)
{
schemaTablesTableNameIndex = idx;
break;
}
Func<string, bool> existsTable = tn =>
{
foreach (DataRow row in schemaTables.Rows)
if (string.Compare(row[schemaTablesTableNameIndex]?.ToString(), tn, true) == 0)
return true;
return false;
//_orm.Ado.ExecuteScalar(CommandType.Text, $" SELECT 1 FROM MsysObjects WHERE Name='{tn}' AND Left([Name],1)<>'~' AND Left([Name],4)<>'Msys' AND Type=1 and Flags=0") != null;
};
Action<string> createTable = tn =>
{
tn = _commonUtils.QuoteSqlName(tn);
//创建表
sb.Append("CREATE TABLE ").Append(tn).Append(" ( ");
foreach (var tbcol in tb.ColumnsByPosition)
{
sb.Append(" \r\n ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name));
if (tbcol.Attribute.IsIdentity == true && tbcol.Attribute.DbType.IndexOf("AUTOINCREMENT", StringComparison.CurrentCultureIgnoreCase) == -1)
sb.Append(" AUTOINCREMENT");
else
sb.Append(" ").Append(tbcol.Attribute.DbType);
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("),");
}
sb.Remove(sb.Length - 1, 1);
sb.Append("\r\n) \r\n;\r\n");
};
Action<string> createTableIndex = tn =>
{
var oldtn = tn;
tn = _commonUtils.QuoteSqlName(tn);
//创建表的索引
foreach (var uk in tb.Indexes)
{
sb.Append("CREATE ");
if (uk.IsUnique) sb.Append("UNIQUE ");
sb.Append("INDEX ").Append(_commonUtils.QuoteSqlName(ReplaceIndexName(uk.Name, oldtn))).Append(" ON ").Append(tn).Append("(");
foreach (var tbcol in uk.Columns)
{
sb.Append(_commonUtils.QuoteSqlName(tbcol.Column.Attribute.Name));
if (tbcol.IsDesc) sb.Append(" DESC");
sb.Append(", ");
}
sb.Remove(sb.Length - 2, 2).Append(");\r\n");
}
};
if (tboldname != null)
{
if (existsTable(tboldname) == false)
//旧表不存在
tboldname = null;
}
isexistsTb = existsTable(tbname);
if (isexistsTb == false)
{ //表不存在
if (tboldname == null)
{
createTable(tbname);
createTableIndex(tbname);
continue;
}
//如果新表,旧表不在一起,创建新表,导入数据,删除旧表
istmpatler = true;
}
if (tboldname != null && isexistsTb == true)
throw new Exception(CoreStrings.S_OldTableExists(tboldname, tbname));
DataTable schemaColumns = null;
DataTable schemaDataTypes = null;
DataTable schemaIndexes = null;
using (var conn = _orm.Ado.MasterPool.Get())
{
schemaColumns = conn.Value.GetSchema("COLUMNS");
schemaDataTypes = conn.Value.GetSchema("DATATYPES");
schemaIndexes = conn.Value.GetSchema("INDEXES");
}
Func<string, string, bool> checkPrimaryKeyByTableNameAndColumn = (tn, cn) =>
{
int table_name_index = 0, primary_key_index = 0, column_name_index = 0;
for (var a = 0; a < schemaIndexes.Columns.Count; a++)
{
switch (schemaIndexes.Columns[a].ColumnName.ToLower())
{
case "table_name":
table_name_index = a;
break;
case "primary_key":
primary_key_index = a;
break;
case "column_name":
column_name_index = a;
break;
}
}
foreach (DataRow row in schemaIndexes.Rows)
{
if (string.Compare(row[table_name_index]?.ToString(), tn, true) != 0) continue;
if (string.Compare(row[column_name_index]?.ToString(), cn, true) != 0) continue;
return new[] { "1", "True", "true", "t", "yes", "ok" }.Contains(row[primary_key_index]?.ToString());
}
return false;
};
Func<string, List<string[]>> getIndexesByTableName = tn =>
{
int table_name_index = 0, index_name_index = 0, primary_key_index = 0, unique_index = 0, column_name_index = 0, collation_index = 0;
for (var a = 0; a < schemaIndexes.Columns.Count; a++)
{
switch (schemaIndexes.Columns[a].ColumnName.ToLower())
{
case "table_name":
table_name_index = a;
break;
case "index_name":
index_name_index = a;
break;
case "primary_key":
primary_key_index = a;
break;
case "unique":
unique_index = a;
break;
case "column_name":
column_name_index = a;
break;
case "collation":
collation_index = a;
break;
}
}
var idxs = new List<string[]>();
foreach (DataRow row in schemaIndexes.Rows)
{
if (string.Compare(row[table_name_index]?.ToString(), tn, true) != 0) continue;
if (new[] { "1", "True", "true", "t", "yes", "ok" }.Contains(row[primary_key_index]?.ToString())) continue;
var column_name = row[column_name_index]?.ToString();
var index_name = row[index_name_index]?.ToString();
var isDesc = int.TryParse(row[collation_index]?.ToString(), out var collation) ? (collation == 2) : false;
var unique = new[] { "1", "True", "true", "t", "yes", "ok" }.Contains(row[unique_index]?.ToString());
idxs.Add(new[] { column_name, index_name, isDesc ? "1" : "0", unique ? "1" : "0" });
}
return idxs;
};
Func<string, Dictionary<string, getColumnsByTableNameResult>> getColumnsByTableName = tn =>
{
int table_name_index = 0, column_name_index = 0, is_nullable_index = 0, data_type_index = 0,
character_maximum_length_index = 0, character_octet_length_index = 0, numeric_precision_index = 0, numeric_scale_index = 0,
datetime_precision_index = 0, description_index = 0;
for (var a = 0; a < schemaColumns.Columns.Count; a++)
{
switch (schemaColumns.Columns[a].ColumnName.ToLower())
{
case "table_name":
table_name_index = a;
break;
case "column_name":
column_name_index = a;
break;
case "is_nullable":
is_nullable_index = a;
break;
case "data_type":
data_type_index = a;
break;
case "character_maximum_length":
character_maximum_length_index = a;
break;
case "character_octet_length":
character_octet_length_index = a;
break;
case "numeric_precision":
numeric_precision_index = a;
break;
case "numeric_scale":
numeric_scale_index = a;
break;
case "datetime_precision":
datetime_precision_index = a;
break;
case "description":
description_index = a;
break;
}
}
int datatype_ProviderDbType_index = 0, datatype_NativeDataType_index = 0, datatype_TypeName_index = 0, datatype_IsAutoIncrementable_index = 0;
for (var a = 0; a < schemaDataTypes.Columns.Count; a++)
{
switch (schemaDataTypes.Columns[a].ColumnName.ToLower())
{
case "providerdbtype":
datatype_ProviderDbType_index = a;
break;
case "nativedatatype":
datatype_NativeDataType_index = a;
break;
case "typename":
datatype_TypeName_index = a;
break;
case "isautoincrementable":
datatype_IsAutoIncrementable_index = a;
break;
}
}
Func<string, DataRow> getDataType = dtnum =>
{
DataRow dtRow = null; //这里的写法是为了照顾 SchemaTypes 返回的结构
foreach (DataRow dataType in schemaDataTypes.Rows)
{
if (datatype_ProviderDbType_index >= 0 && dataType[datatype_ProviderDbType_index]?.ToString() == dtnum) dtRow = dataType;
if (datatype_NativeDataType_index >= 0 && dataType[datatype_NativeDataType_index]?.ToString() == dtnum) dtRow = dataType;
}
return dtRow;
};
var ret = new Dictionary<string, getColumnsByTableNameResult>();
foreach (DataRow row in schemaColumns.Rows)
{
if (string.Compare(row[table_name_index]?.ToString(), tn, true) != 0) continue;
var column_name = row[column_name_index]?.ToString();
var dataTypeRow = getDataType(row[data_type_index]?.ToString());
var is_identity = new[] { "1", "True", "true", "t", "yes", "ok" }.Contains(dataTypeRow[datatype_IsAutoIncrementable_index]?.ToString());
var is_nullable = new[] { "1", "True", "true", "t", "yes", "ok" }.Contains(row[is_nullable_index]?.ToString());
if (is_nullable && checkPrimaryKeyByTableNameAndColumn(tn, column_name)) is_nullable = false;
var comment = row[description_index]?.ToString();
if (string.IsNullOrEmpty(comment)) comment = null;
int.TryParse(row[character_maximum_length_index]?.ToString(), out var character_maximum_length);
int.TryParse(row[character_octet_length_index]?.ToString(), out var character_octet_length);
int.TryParse(row[numeric_precision_index]?.ToString(), out var numeric_precision);
int.TryParse(row[numeric_scale_index]?.ToString(), out var numeric_scale);
int.TryParse(row[datetime_precision_index]?.ToString(), out var datetime_precision);
var datatype = dataTypeRow[datatype_TypeName_index]?.ToString().ToUpper();
if (numeric_precision > 0 && numeric_scale > 0) datatype = $"{datatype}({numeric_precision},{numeric_scale})";
else if (character_maximum_length > 0 && character_octet_length > 0) datatype = $"{datatype}({character_maximum_length})";
ret.Add(column_name, new getColumnsByTableNameResult(column_name, datatype, is_nullable, is_identity, comment));
}
return ret;
};
//对比字段,只可以修改类型、增加字段、有限的修改字段名;保证安全不删除字段
var tbtmp = tboldname ?? tbname;
var tbstruct = getColumnsByTableName(tbtmp);
if (istmpatler == false)
{
foreach (var tbcol in tb.ColumnsByPosition)
{
if (istmpatler) break;
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))
{
if (tbstructcol.sqlType != "LONG" && tbcol.Attribute.DbType.StartsWith(tbstructcol.sqlType, StringComparison.CurrentCultureIgnoreCase) == false)
istmpatler = true;
if (tbstructcol.sqlType != "BIT" && tbcol.Attribute.IsNullable != tbstructcol.is_nullable)
istmpatler = true;
if (tbcol.Attribute.IsIdentity != tbstructcol.is_identity)
istmpatler = true;
if (string.Compare(tbstructcol.column, tbcol.Attribute.OldName, true) == 0)
//修改列名
istmpatler = true;
continue;
}
//添加列
istmpatler = true;
}
}
if (istmpatler == false)
{
var dsuk = getIndexesByTableName(tbtmp);
foreach (var uk in tb.Indexes)
{
if (string.IsNullOrEmpty(uk.Name) || uk.Columns.Any() == false) continue;
var ukname = ReplaceIndexName(uk.Name, tbname);
var dsukfind1 = dsuk.Where(a => string.Compare(a[1], ukname, true) == 0).ToArray();
if (dsukfind1.Any() == false || dsukfind1.Length != uk.Columns.Length || dsukfind1.Where(a => (a[3] == "1") == uk.IsUnique && uk.Columns.Where(b => string.Compare(b.Column.Attribute.Name, a[0], true) == 0 && (a[2] == "1") == b.IsDesc).Any()).Count() != uk.Columns.Length)
istmpatler = true;
}
}
if (istmpatler == false)
{
sb.Append(sbalter);
continue;
}
Dictionary<string, bool> dicDropTable = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
Action<string> dropTable = tn =>
{
if (dicDropTable.ContainsKey(tn)) return;
dicDropTable.Add(tn, true);
sb.Append("DROP TABLE ").Append(_commonUtils.QuoteSqlName(tn)).Append(";\r\n");
};
Action<string, string> createTableImportData = (newtn, oldtn) =>
{
if (existsTable(newtn)) dropTable(newtn);
createTable(newtn);
sb.Append("INSERT INTO ").Append(_commonUtils.QuoteSqlName(newtn)).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 = MsAccessUtils.GetCastSql(insertvalue, tbcol.Attribute.MapType);
}
if (tbcol.Attribute.IsNullable != tbstructcol.is_nullable)
insertvalue = $"iif(isnull({insertvalue}),{tbcol.DbDefaultValue},{insertvalue})";
}
else if (tbcol.Attribute.IsNullable == false)
insertvalue = tbcol.DbDefaultValue;
sb.Append(insertvalue).Append(", ");
}
sb.Remove(sb.Length - 2, 2).Append(" FROM ").Append(_commonUtils.QuoteSqlName(oldtn)).Append(";\r\n");
dropTable(oldtn);
};
if (tboldname != null && isexistsTb == true)
{
createTableImportData(tbname + "_FreeSqlBackup", tbname); //备份 tbname 表
createTableImportData(tbname, tboldname); //创建新表,把 oldname 旧表数据导入到新表,删除 oldname
}
if (tboldname != null && isexistsTb == false)
{
createTableImportData(tbname, tboldname); //创建新表,把 oldname 旧表数据导入到新表,删除 oldname
}
if (tboldname == null && isexistsTb == true)
{
createTableImportData(tbname + "_FreeSqlTmp", tbname); //创建 Tmp 表,把 tbname 表数据导入到 Tmp删除 tbname
createTableImportData(tbname, tbname + "_FreeSqlTmp"); //创建 新表,把 Tmp 表数据导入到新表,删除 Tmp
}
}
return sb.Length == 0 ? null : sb.ToString();
}
class getColumnsByTableNameResult
{
public string column { get; }
public string sqlType { get; }
public bool is_nullable { get; }
public bool is_identity { get; }
public string comment { get; }
public getColumnsByTableNameResult(string column, string sqlType, bool is_nullable, bool is_identity, string comment)
{
this.column = column;
this.sqlType = sqlType;
this.is_nullable = is_nullable;
this.is_identity = is_identity;
this.comment = comment;
}
}
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;
var affrows = 0;
foreach (var script in scripts)
affrows += base.ExecuteDDLStatements(script);
return affrows;
}
}
}