1091 lines
46 KiB
C#
1091 lines
46 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using NewLife;
|
||
using NewLife.Collections;
|
||
using NewLife.Log;
|
||
using NewLife.Reflection;
|
||
using XCode.DataAccessLayer;
|
||
|
||
namespace XCode.Code
|
||
{
|
||
/// <summary>实体类生成器</summary>
|
||
public class EntityBuilder : ClassBuilder
|
||
{
|
||
#region 属性
|
||
/// <summary>泛型实体类。泛型参数名TEntity</summary>
|
||
public Boolean GenericType { get; set; }
|
||
|
||
/// <summary>业务类。</summary>
|
||
public Boolean Business { get; set; }
|
||
|
||
/// <summary>所有表类型名。用于扩展属性</summary>
|
||
public IList<IDataTable> AllTables { get; set; } = new List<IDataTable>();
|
||
#endregion
|
||
|
||
#region 静态快速
|
||
/// <summary>为Xml模型文件生成实体类</summary>
|
||
/// <param name="xmlFile">模型文件</param>
|
||
/// <param name="output">输出目录</param>
|
||
/// <param name="nameSpace">命名空间</param>
|
||
/// <param name="connName">连接名</param>
|
||
/// <param name="chineseFileName">中文文件名</param>
|
||
public static Int32 Build(String xmlFile = null, String output = null, String nameSpace = null, String connName = null, Boolean? chineseFileName = null)
|
||
{
|
||
if (xmlFile.IsNullOrEmpty())
|
||
{
|
||
var di = ".".GetBasePath().AsDirectory();
|
||
xmlFile = di.GetFiles("*.xml", SearchOption.TopDirectoryOnly).FirstOrDefault()?.FullName;
|
||
}
|
||
|
||
xmlFile = xmlFile.GetBasePath();
|
||
if (!File.Exists(xmlFile)) throw new FileNotFoundException("指定模型文件不存在!", xmlFile);
|
||
|
||
var option = new BuilderOption
|
||
{
|
||
Output = output,
|
||
Namespace = nameSpace,
|
||
ConnName = connName,
|
||
};
|
||
|
||
// 导入模型
|
||
var tables = LoadModels(xmlFile, option, out var atts);
|
||
if (tables.Count == 0) return 0;
|
||
|
||
// 反哺。确保输出空特性
|
||
atts["Output"] = option.Output + "";
|
||
atts["NameSpace"] = option.Namespace + "";
|
||
atts["ConnName"] = option.ConnName + "";
|
||
atts["BaseClass"] = option.BaseClass + "";
|
||
atts.Remove("NameIgnoreCase");
|
||
atts.Remove("IgnoreNameCase");
|
||
|
||
// 中文文件名
|
||
if (chineseFileName != null)
|
||
atts["ChineseFileName"] = chineseFileName.Value ? "True" : "False";
|
||
else
|
||
chineseFileName = atts["ChineseFileName"].ToBoolean(true);
|
||
|
||
var rs = BuildTables(tables, option, chineseFileName.Value);
|
||
|
||
// 更新xsd
|
||
atts["xmlns"] = atts["xmlns"].Replace("ModelSchema", "Model2020");
|
||
atts["xs:schemaLocation"] = atts["xs:schemaLocation"].Replace("ModelSchema", "Model2020");
|
||
|
||
// 保存模型文件
|
||
var xmlContent = File.ReadAllText(xmlFile);
|
||
var xml2 = ModelHelper.ToXml(tables, atts);
|
||
if (xmlContent != xml2) File.WriteAllText(xmlFile, xml2);
|
||
|
||
return rs;
|
||
}
|
||
|
||
/// <summary>为Xml模型文件生成实体类</summary>
|
||
/// <param name="tables">模型文件</param>
|
||
/// <param name="option">生成可选项</param>
|
||
/// <param name="chineseFileName">是否中文名称</param>
|
||
public static Int32 BuildTables(IList<IDataTable> tables, BuilderOption option, Boolean chineseFileName = true)
|
||
{
|
||
if (tables == null || tables.Count == 0) return 0;
|
||
|
||
//output = output.GetBasePath();
|
||
|
||
//// 连接名
|
||
//if (connName.IsNullOrEmpty() && !nameSpace.IsNullOrEmpty() && nameSpace.Contains(".")) connName = nameSpace.Substring(nameSpace.LastIndexOf(".") + 1);
|
||
|
||
//XTrace.WriteLine("代码生成:{0} 输出:{1} 命名空间:{2} 连接名:{3} 基类:{4}", tables.Count, output, nameSpace, connName, baseClass);
|
||
|
||
var count = 0;
|
||
foreach (var item in tables)
|
||
{
|
||
var builder = new EntityBuilder { AllTables = tables, };
|
||
if (option != null) builder.Option = option;
|
||
|
||
builder.Load(item);
|
||
|
||
//var option = builder.Option;
|
||
//if (!output.IsNullOrEmpty()) option.Output = output;
|
||
//if (!nameSpace.IsNullOrEmpty()) option.Namespace = nameSpace;
|
||
//if (!connName.IsNullOrEmpty()) builder.ConnName = connName;
|
||
//if (!baseClass.IsNullOrEmpty()) option.BaseClass = baseClass;
|
||
|
||
builder.Execute();
|
||
builder.Save(null, true, chineseFileName);
|
||
|
||
builder.Business = true;
|
||
builder.Execute();
|
||
builder.Save(null, false, chineseFileName);
|
||
|
||
count++;
|
||
}
|
||
|
||
return count;
|
||
}
|
||
#endregion
|
||
|
||
#region 方法
|
||
/// <summary>加载数据表</summary>
|
||
/// <param name="table"></param>
|
||
public void Load(IDataTable table)
|
||
{
|
||
Table = table;
|
||
|
||
var option = Option;
|
||
|
||
// 命名空间
|
||
var str = table.Properties["Namespace"];
|
||
if (!str.IsNullOrEmpty()) option.Namespace = str;
|
||
|
||
// 连接名
|
||
var connName = table.ConnName;
|
||
if (connName.IsNullOrEmpty() && !option.Namespace.IsNullOrEmpty())
|
||
{
|
||
var p = option.Namespace.LastIndexOf('.');
|
||
if (p > 0) connName = option.Namespace.Substring(p + 1);
|
||
}
|
||
if (!connName.IsNullOrEmpty()) option.ConnName = connName;
|
||
|
||
// 基类
|
||
str = table.Properties["BaseClass"];
|
||
if (!str.IsNullOrEmpty()) option.BaseClass = str;
|
||
|
||
// 泛型实体类
|
||
str = table.Properties["RenderGenEntity"];
|
||
if (!str.IsNullOrEmpty()) GenericType = str.ToBoolean();
|
||
|
||
//// 名称忽略大小写(默认忽略)
|
||
//if (item.IgnoreNameCase.IsNullOrEmpty() && !ignoreNameCase) item.IgnoreNameCase = ignoreNameCase + "";
|
||
//item.Properties.Remove("NameIgnoreCase");
|
||
|
||
// 输出目录
|
||
str = table.Properties["Output"];
|
||
if (!str.IsNullOrEmpty()) option.Output = str.GetBasePath();
|
||
}
|
||
#endregion
|
||
|
||
#region 基础
|
||
/// <summary>执行生成</summary>
|
||
public override void Execute()
|
||
{
|
||
// 增加常用命名空间
|
||
AddNameSpace();
|
||
|
||
if (ClassName.IsNullOrEmpty()) ClassName = (Option.Interface ? ("I" + Table.Name) : Table.Name) + Option.ClassPrefix;
|
||
if (GenericType) ClassName += "<TEntity>";
|
||
|
||
base.Execute();
|
||
}
|
||
|
||
/// <summary>实体类头部</summary>
|
||
protected override void BuildClassHeader()
|
||
{
|
||
// 泛型实体类增加默认实例
|
||
if (GenericType && Business)
|
||
{
|
||
WriteLine("/// <summary>{0}</summary>", Table.DisplayName);
|
||
WriteLine("[Serializable]");
|
||
WriteLine("[ModelCheckMode(ModelCheckModes.CheckTableWhenFirstUse)]");
|
||
WriteLine("public class {0} : {0}<{0}> {{ }}", Table.Name);
|
||
WriteLine();
|
||
}
|
||
|
||
base.BuildClassHeader();
|
||
}
|
||
|
||
///// <summary>获取类名</summary>
|
||
///// <returns></returns>
|
||
//protected override String GetClassName()
|
||
//{
|
||
// // 类名
|
||
// var name = base.GetClassName();
|
||
// if (GenericType) name += "<TEntity>";
|
||
|
||
// return name;
|
||
//}
|
||
|
||
/// <summary>获取基类</summary>
|
||
/// <returns></returns>
|
||
protected override String GetBaseClass()
|
||
{
|
||
// 数据类的基类只有接口,业务类基类则比较复杂
|
||
if (!Business) return "I" + Table.Name;
|
||
|
||
var name = Option.BaseClass;
|
||
if (name.IsNullOrEmpty()) name = "Entity";
|
||
|
||
if (GenericType)
|
||
name = "{0}<TEntity> where TEntity : {1}<TEntity>, new()".F(name, Table.Name);
|
||
else
|
||
name = "{0}<{1}>".F(name, Table.Name);
|
||
|
||
return name;
|
||
|
||
//return base.GetBaseClass();
|
||
}
|
||
|
||
/// <summary>保存</summary>
|
||
/// <param name="ext"></param>
|
||
/// <param name="overwrite"></param>
|
||
/// <param name="chineseFileName"></param>
|
||
public override String Save(String ext = null, Boolean overwrite = true, Boolean chineseFileName = true)
|
||
{
|
||
if (ext.IsNullOrEmpty() && Business)
|
||
{
|
||
ext = ".Biz.cs";
|
||
//overwrite = false;
|
||
}
|
||
|
||
return base.Save(ext, overwrite, chineseFileName);
|
||
}
|
||
|
||
/// <summary>生成尾部</summary>
|
||
protected override void OnExecuted()
|
||
{
|
||
// 类接口
|
||
WriteLine("}");
|
||
|
||
if (!Business)
|
||
{
|
||
WriteLine();
|
||
BuildInterface();
|
||
}
|
||
|
||
if (!Option.Namespace.IsNullOrEmpty())
|
||
{
|
||
Writer.Write("}");
|
||
}
|
||
}
|
||
|
||
/// <summary>增加常用命名空间</summary>
|
||
protected virtual void AddNameSpace()
|
||
{
|
||
var us = Option.Usings;
|
||
|
||
us.Add("XCode");
|
||
us.Add("XCode.Configuration");
|
||
us.Add("XCode.DataAccessLayer");
|
||
|
||
if (Business && !Option.Pure && !us.Contains("System.Web"))
|
||
{
|
||
us.Add("System.IO");
|
||
us.Add("System.Linq");
|
||
us.Add("System.Reflection");
|
||
us.Add("System.Text");
|
||
us.Add("System.Threading.Tasks");
|
||
us.Add("System.Web");
|
||
us.Add("System.Web.Script.Serialization");
|
||
us.Add("System.Xml.Serialization");
|
||
us.Add("System.Runtime.Serialization");
|
||
|
||
us.Add("NewLife");
|
||
us.Add("NewLife.Data");
|
||
us.Add("NewLife.Model");
|
||
us.Add("NewLife.Log");
|
||
us.Add("NewLife.Reflection");
|
||
us.Add("NewLife.Threading");
|
||
us.Add("NewLife.Web");
|
||
us.Add("XCode.Cache");
|
||
us.Add("XCode.Membership");
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region 数据类
|
||
/// <summary>实体类头部</summary>
|
||
protected override void BuildAttribute()
|
||
{
|
||
if (Business)
|
||
{
|
||
WriteLine("/// <summary>{0}</summary>", Table.Description);
|
||
return;
|
||
}
|
||
|
||
base.BuildAttribute();
|
||
|
||
var dt = Table;
|
||
foreach (var item in dt.Indexes)
|
||
{
|
||
WriteLine("[BindIndex(\"{0}\", {1}, \"{2}\")]", item.Name, item.Unique.ToString().ToLower(), item.Columns.Join());
|
||
}
|
||
|
||
var cn = dt.Properties["ConnName"];
|
||
if (cn.IsNullOrEmpty()) cn = Option.ConnName;
|
||
WriteLine("[BindTable(\"{0}\", Description = \"{1}\", ConnName = \"{2}\", DbType = DatabaseType.{3})]", dt.TableName, dt.Description, cn, dt.DbType);
|
||
}
|
||
|
||
/// <summary>生成每一项</summary>
|
||
protected override void BuildItem(IDataColumn column)
|
||
{
|
||
var dc = column;
|
||
|
||
var type = dc.Properties["Type"];
|
||
if (type.IsNullOrEmpty()) type = dc.DataType?.Name;
|
||
|
||
// 字段
|
||
WriteLine("private {0} _{1};", type, dc.Name);
|
||
|
||
// 注释
|
||
var des = dc.Description;
|
||
WriteLine("/// <summary>{0}</summary>", des);
|
||
|
||
// 附加特性
|
||
if (dc.Properties.TryGetValue("Attribute", out var att))
|
||
WriteLine("[{0}]", att.Replace("{name}", dc.Name));
|
||
|
||
if (!Option.Pure)
|
||
{
|
||
var dis = dc.DisplayName;
|
||
if (!dis.IsNullOrEmpty()) WriteLine("[DisplayName(\"{0}\")]", dis);
|
||
|
||
if (!des.IsNullOrEmpty()) WriteLine("[Description(\"{0}\")]", des);
|
||
}
|
||
|
||
WriteLine("[DataObjectField({0}, {1}, {2}, {3})]", dc.PrimaryKey.ToString().ToLower(), dc.Identity.ToString().ToLower(), dc.Nullable.ToString().ToLower(), dc.Length);
|
||
|
||
// 支持生成带精度的特性
|
||
if (!dc.ItemType.IsNullOrEmpty())
|
||
WriteLine("[BindColumn(\"{0}\", \"{1}\", \"{2}\", ItemType = \"{3}\")]", dc.ColumnName, dc.Description, dc.RawType, dc.ItemType);
|
||
else if (dc.Precision > 0 || dc.Scale > 0)
|
||
WriteLine("[BindColumn(\"{0}\", \"{1}\", \"{2}\", Precision = {3}, Scale = {4})]", dc.ColumnName, dc.Description, dc.RawType, dc.Precision, dc.Scale);
|
||
else
|
||
WriteLine("[BindColumn(\"{0}\", \"{1}\", \"{2}\"{3})]", dc.ColumnName, dc.Description, dc.RawType, dc.Master ? ", Master = true" : "");
|
||
|
||
if (Option.Interface)
|
||
WriteLine("{0} {1} {{ get; set; }}", type, dc.Name);
|
||
else
|
||
WriteLine("public {0} {1} {{ get => _{1}; set {{ if (OnPropertyChanging(\"{1}\", value)) {{ _{1} = value; OnPropertyChanged(\"{1}\"); }} }} }}", type, dc.Name);
|
||
}
|
||
|
||
/// <summary>生成主体</summary>
|
||
protected override void BuildItems()
|
||
{
|
||
if (Business)
|
||
BuildBiz();
|
||
else
|
||
{
|
||
base.BuildItems();
|
||
|
||
WriteLine();
|
||
BuildIndex();
|
||
|
||
WriteLine();
|
||
BuildFieldName();
|
||
}
|
||
}
|
||
|
||
private void BuildIndex()
|
||
{
|
||
WriteLine("#region 获取/设置 字段值");
|
||
WriteLine("/// <summary>获取/设置 字段值</summary>");
|
||
WriteLine("/// <param name=\"name\">字段名</param>");
|
||
WriteLine("/// <returns></returns>");
|
||
WriteLine("public override Object this[String name]");
|
||
WriteLine("{");
|
||
|
||
// get
|
||
{
|
||
WriteLine("get");
|
||
WriteLine("{");
|
||
{
|
||
WriteLine("switch (name)");
|
||
WriteLine("{");
|
||
foreach (var dc in Table.Columns)
|
||
{
|
||
WriteLine("case \"{0}\": return _{0};", dc.Name);
|
||
}
|
||
WriteLine("default: return base[name];");
|
||
WriteLine("}");
|
||
}
|
||
WriteLine("}");
|
||
}
|
||
|
||
// set
|
||
WriteLine("set");
|
||
WriteLine("{");
|
||
{
|
||
WriteLine("switch (name)");
|
||
WriteLine("{");
|
||
var conv = typeof(Convert);
|
||
foreach (var dc in Table.Columns)
|
||
{
|
||
var type = dc.Properties["Type"];
|
||
if (type.IsNullOrEmpty()) type = dc.DataType?.Name;
|
||
|
||
if (!type.IsNullOrEmpty())
|
||
{
|
||
if (!type.Contains("."))
|
||
{
|
||
|
||
}
|
||
if (!type.Contains(".") && conv.GetMethod("To" + type, new Type[] { typeof(Object) }) != null)
|
||
{
|
||
switch (type)
|
||
{
|
||
case "Int32":
|
||
WriteLine("case \"{0}\": _{0} = value.ToInt(); break;", dc.Name);
|
||
break;
|
||
case "Int64":
|
||
WriteLine("case \"{0}\": _{0} = value.ToLong(); break;", dc.Name);
|
||
break;
|
||
case "Double":
|
||
WriteLine("case \"{0}\": _{0} = value.ToDouble(); break;", dc.Name);
|
||
break;
|
||
case "Boolean":
|
||
WriteLine("case \"{0}\": _{0} = value.ToBoolean(); break;", dc.Name);
|
||
break;
|
||
case "DateTime":
|
||
WriteLine("case \"{0}\": _{0} = value.ToDateTime(); break;", dc.Name);
|
||
break;
|
||
default:
|
||
WriteLine("case \"{0}\": _{0} = Convert.To{1}(value); break;", dc.Name, type);
|
||
break;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
try
|
||
{
|
||
// 特殊支持枚举
|
||
var type2 = type.GetTypeEx(false);
|
||
if (type2 != null && type2.IsEnum)
|
||
WriteLine("case \"{0}\": _{0} = ({1})value.ToInt(); break;", dc.Name, type);
|
||
else
|
||
WriteLine("case \"{0}\": _{0} = ({1})value; break;", dc.Name, type);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
XTrace.WriteException(ex);
|
||
WriteLine("case \"{0}\": _{0} = ({1})value; break;", dc.Name, type);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
WriteLine("default: base[name] = value; break;");
|
||
WriteLine("}");
|
||
}
|
||
WriteLine("}");
|
||
|
||
WriteLine("}");
|
||
WriteLine("#endregion");
|
||
}
|
||
|
||
private void BuildFieldName()
|
||
{
|
||
WriteLine("#region 字段名");
|
||
|
||
WriteLine("/// <summary>取得{0}字段信息的快捷方式</summary>", Table.DisplayName);
|
||
WriteLine("public partial class _");
|
||
WriteLine("{");
|
||
foreach (var dc in Table.Columns)
|
||
{
|
||
WriteLine("/// <summary>{0}</summary>", dc.Description);
|
||
WriteLine("public static readonly Field {0} = FindByName(\"{0}\");", dc.Name);
|
||
WriteLine();
|
||
}
|
||
WriteLine("static Field FindByName(String name) => Meta.Table.FindByName(name);");
|
||
WriteLine("}");
|
||
|
||
WriteLine();
|
||
|
||
WriteLine("/// <summary>取得{0}字段名称的快捷方式</summary>", Table.DisplayName);
|
||
WriteLine("public partial class __");
|
||
WriteLine("{");
|
||
var k = Table.Columns.Count;
|
||
foreach (var dc in Table.Columns)
|
||
{
|
||
WriteLine("/// <summary>{0}</summary>", dc.Description);
|
||
WriteLine("public const String {0} = \"{0}\";", dc.Name);
|
||
if (--k > 0) WriteLine();
|
||
}
|
||
WriteLine("}");
|
||
|
||
WriteLine("#endregion");
|
||
}
|
||
|
||
private void BuildInterface()
|
||
{
|
||
var dt = Table;
|
||
WriteLine("/// <summary>{0}接口</summary>", dt.Description);
|
||
WriteLine("public partial interface I{0}", dt.Name);
|
||
WriteLine("{");
|
||
|
||
WriteLine("#region 属性");
|
||
var k = Table.Columns.Count;
|
||
foreach (var dc in Table.Columns)
|
||
{
|
||
var type = dc.Properties["Type"];
|
||
if (type.IsNullOrEmpty()) type = dc.DataType?.Name;
|
||
|
||
WriteLine("/// <summary>{0}</summary>", dc.Description);
|
||
WriteLine("{0} {1} {{ get; set; }}", type, dc.Name);
|
||
if (--k > 0) WriteLine();
|
||
}
|
||
WriteLine("#endregion");
|
||
|
||
WriteLine();
|
||
WriteLine("#region 获取/设置 字段值");
|
||
WriteLine("/// <summary>获取/设置 字段值</summary>");
|
||
WriteLine("/// <param name=\"name\">字段名</param>");
|
||
WriteLine("/// <returns></returns>");
|
||
WriteLine("Object this[String name] { get; set; }");
|
||
WriteLine("#endregion");
|
||
|
||
WriteLine("}");
|
||
}
|
||
#endregion
|
||
|
||
#region 业务类
|
||
/// <summary>生成实体类业务部分</summary>
|
||
protected virtual void BuildBiz()
|
||
{
|
||
BuildAction();
|
||
|
||
WriteLine();
|
||
BuildExtendProperty();
|
||
|
||
WriteLine();
|
||
BuildExtendSearch();
|
||
|
||
WriteLine();
|
||
BuildSearch();
|
||
|
||
WriteLine();
|
||
BuildBusiness();
|
||
}
|
||
|
||
/// <summary>对象操作</summary>
|
||
protected virtual void BuildAction()
|
||
{
|
||
WriteLine("#region 对象操作");
|
||
|
||
// 静态构造函数
|
||
BuildCctor();
|
||
|
||
// 验证函数
|
||
WriteLine();
|
||
BuildValid();
|
||
|
||
// 初始化数据
|
||
WriteLine();
|
||
BuildInitData();
|
||
|
||
// 重写添删改
|
||
WriteLine();
|
||
BuildOverride();
|
||
|
||
WriteLine("#endregion");
|
||
}
|
||
|
||
/// <summary>生成静态构造函数</summary>
|
||
protected virtual void BuildCctor()
|
||
{
|
||
WriteLine("static {0}()", Table.Name);
|
||
WriteLine("{");
|
||
{
|
||
if (GenericType)
|
||
{
|
||
WriteLine("// 用于引发基类的静态构造函数,所有层次的泛型实体类都应该有一个");
|
||
WriteLine("var entity = new TEntity();");
|
||
WriteLine();
|
||
}
|
||
|
||
// 第一个非自增非主键整型字段,生成累加字段代码
|
||
var dc = Table.Columns.FirstOrDefault(e => !e.Identity && !e.PrimaryKey && (e.DataType == typeof(Int32) || e.DataType == typeof(Int64)));
|
||
if (dc != null)
|
||
{
|
||
WriteLine("// 累加字段,生成 Update xx Set Count=Count+1234 Where xxx");
|
||
WriteLine("//var df = Meta.Factory.AdditionalFields;");
|
||
WriteLine("//df.Add(nameof({0}));", dc.Name);
|
||
}
|
||
|
||
var ns = new HashSet<String>(Table.Columns.Select(e => e.Name), StringComparer.OrdinalIgnoreCase);
|
||
WriteLine();
|
||
WriteLine("// 过滤器 UserModule、TimeModule、IPModule");
|
||
if (ns.Contains("CreateUserID") || ns.Contains("CreateUser") || ns.Contains("UpdateUserID") || ns.Contains("UpdateUser"))
|
||
WriteLine("Meta.Modules.Add<UserModule>();");
|
||
if (ns.Contains("CreateTime") || ns.Contains("UpdateTime"))
|
||
WriteLine("Meta.Modules.Add<TimeModule>();");
|
||
if (ns.Contains("CreateIP") || ns.Contains("UpdateIP"))
|
||
WriteLine("Meta.Modules.Add<IPModule>();");
|
||
|
||
// 唯一索引不是主键,又刚好是Master,使用单对象缓存从键
|
||
var di = Table.Indexes.FirstOrDefault(e => e.Unique && e.Columns.Length == 1 && Table.GetColumn(e.Columns[0]).Master);
|
||
if (di != null)
|
||
{
|
||
dc = Table.GetColumn(di.Columns[0]);
|
||
|
||
WriteLine();
|
||
WriteLine("// 单对象缓存");
|
||
WriteLine("var sc = Meta.SingleCache;");
|
||
WriteLine("sc.FindSlaveKeyMethod = k => Find(_.{0} == k);", dc.Name);
|
||
WriteLine("sc.GetSlaveKeyMethod = e => e.{0};", dc.Name);
|
||
}
|
||
}
|
||
WriteLine("}");
|
||
}
|
||
|
||
/// <summary>数据验证</summary>
|
||
protected virtual void BuildValid()
|
||
{
|
||
WriteLine("/// <summary>验证数据,通过抛出异常的方式提示验证失败。</summary>");
|
||
WriteLine("/// <param name=\"isNew\">是否插入</param>");
|
||
WriteLine("public override void Valid(Boolean isNew)");
|
||
WriteLine("{");
|
||
{
|
||
WriteLine("// 如果没有脏数据,则不需要进行任何处理");
|
||
WriteLine("if (!HasDirty) return;");
|
||
|
||
// 非空判断
|
||
var cs = Table.Columns.Where(e => !e.Nullable && e.DataType == typeof(String)).ToArray();
|
||
if (cs.Length > 0)
|
||
{
|
||
WriteLine();
|
||
WriteLine("// 这里验证参数范围,建议抛出参数异常,指定参数名,前端用户界面可以捕获参数异常并聚焦到对应的参数输入框");
|
||
foreach (var item in cs)
|
||
{
|
||
WriteLine("if ({0}.IsNullOrEmpty()) throw new ArgumentNullException({1}, \"{2}不能为空!\");", item.Name, NameOf(item.Name), item.DisplayName ?? item.Name);
|
||
}
|
||
}
|
||
|
||
//WriteLine();
|
||
//WriteLine("// 建议先调用基类方法,基类方法会对唯一索引的数据进行验证");
|
||
//WriteLine("base.Valid(isNew);");
|
||
|
||
WriteLine();
|
||
WriteLine("// 在新插入数据或者修改了指定字段时进行修正");
|
||
|
||
// 货币类型保留小数位数
|
||
cs = Table.Columns.Where(e => e.DataType == typeof(Decimal)).ToArray();
|
||
if (cs.Length > 0)
|
||
{
|
||
WriteLine("// 货币保留6位小数");
|
||
foreach (var item in cs)
|
||
{
|
||
WriteLine("{0} = Math.Round({0}, 6);", item.Name);
|
||
}
|
||
}
|
||
|
||
// 处理当前已登录用户信息
|
||
cs = Table.Columns.Where(e => e.DataType == typeof(Int32) && e.Name.EqualIgnoreCase("CreateUserID", "UpdateUserID")).ToArray();
|
||
if (cs.Length > 0)
|
||
{
|
||
WriteLine("// 处理当前已登录用户信息,可以由UserModule过滤器代劳");
|
||
WriteLine("/*var user = ManageProvider.User;");
|
||
WriteLine("if (user != null)");
|
||
WriteLine("{");
|
||
foreach (var item in cs)
|
||
{
|
||
if (item.Name.EqualIgnoreCase("CreateUserID"))
|
||
WriteLine("if (isNew && !Dirtys[{0}]) {1} = user.ID;", NameOf(item.Name), item.Name);
|
||
else
|
||
WriteLine("if (!Dirtys[{0}]) {1} = user.ID;", NameOf(item.Name), item.Name);
|
||
}
|
||
WriteLine("}*/");
|
||
}
|
||
|
||
var dc = Table.Columns.FirstOrDefault(e => e.Name.EqualIgnoreCase("CreateTime"));
|
||
if (dc != null) WriteLine("//if (isNew && !Dirtys[{0}]) {1} = DateTime.Now;", NameOf(dc.Name), dc.Name);
|
||
|
||
dc = Table.Columns.FirstOrDefault(e => e.Name.EqualIgnoreCase("UpdateTime"));
|
||
if (dc != null) WriteLine("//if (!Dirtys[{0}]) {1} = DateTime.Now;", NameOf(dc.Name), dc.Name);
|
||
|
||
dc = Table.Columns.FirstOrDefault(e => e.Name.EqualIgnoreCase("CreateIP"));
|
||
if (dc != null) WriteLine("//if (isNew && !Dirtys[{0}]) {1} = ManageProvider.UserHost;", NameOf(dc.Name), dc.Name);
|
||
|
||
dc = Table.Columns.FirstOrDefault(e => e.Name.EqualIgnoreCase("UpdateIP"));
|
||
if (dc != null) WriteLine("//if (!Dirtys[{0}]) {1} = ManageProvider.UserHost;", NameOf(dc.Name), dc.Name);
|
||
|
||
// 唯一索引检查唯一性
|
||
var dis = Table.Indexes.Where(e => e.Unique).ToArray();
|
||
if (dis.Length > 0)
|
||
{
|
||
WriteLine();
|
||
WriteLine("// 检查唯一索引");
|
||
foreach (var item in dis)
|
||
{
|
||
//WriteLine("if (!_IsFromDatabase) CheckExist(isNew, {0});", Table.GetColumns(item.Columns).Select(e => "__." + e.Name).Join(", "));
|
||
WriteLine("// CheckExist(isNew, {0});", Table.GetColumns(item.Columns).Select(e => $"nameof({e.Name})").Join(", "));
|
||
}
|
||
}
|
||
}
|
||
WriteLine("}");
|
||
}
|
||
|
||
/// <summary>初始化数据</summary>
|
||
protected virtual void BuildInitData()
|
||
{
|
||
var name = GenericType ? "TEntity" : Table.Name;
|
||
|
||
WriteLine("///// <summary>首次连接数据库时初始化数据,仅用于实体类重载,用户不应该调用该方法</summary>");
|
||
WriteLine("//[EditorBrowsable(EditorBrowsableState.Never)]");
|
||
//zilo555 去掉internal
|
||
WriteLine("//protected override void InitData()");
|
||
WriteLine("//{");
|
||
WriteLine("// // InitData一般用于当数据表没有数据时添加一些默认数据,该实体类的任何第一次数据库操作都会触发该方法,默认异步调用");
|
||
WriteLine("// if (Meta.Session.Count > 0) return;");
|
||
WriteLine();
|
||
WriteLine("// if (XTrace.Debug) XTrace.WriteLine(\"开始初始化{0}[{1}]数据……\");", name, Table.DisplayName);
|
||
WriteLine();
|
||
WriteLine("// var entity = new {0}();", name);
|
||
foreach (var dc in Table.Columns)
|
||
{
|
||
switch (dc.DataType.GetTypeCode())
|
||
{
|
||
case TypeCode.Boolean:
|
||
WriteLine("// entity.{0} = true;", dc.Name);
|
||
break;
|
||
case TypeCode.SByte:
|
||
case TypeCode.Byte:
|
||
case TypeCode.Int16:
|
||
case TypeCode.UInt16:
|
||
case TypeCode.Int32:
|
||
case TypeCode.UInt32:
|
||
case TypeCode.Int64:
|
||
case TypeCode.UInt64:
|
||
WriteLine("// entity.{0} = 0;", dc.Name);
|
||
break;
|
||
case TypeCode.Single:
|
||
case TypeCode.Double:
|
||
case TypeCode.Decimal:
|
||
WriteLine("// entity.{0} = 0.0;", dc.Name);
|
||
break;
|
||
case TypeCode.DateTime:
|
||
WriteLine("// entity.{0} = DateTime.Now;", dc.Name);
|
||
break;
|
||
case TypeCode.String:
|
||
WriteLine("// entity.{0} = \"abc\";", dc.Name);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
WriteLine("// entity.Insert();");
|
||
WriteLine();
|
||
WriteLine("// if (XTrace.Debug) XTrace.WriteLine(\"完成初始化{0}[{1}]数据!\");", name, Table.DisplayName);
|
||
WriteLine("//}");
|
||
}
|
||
|
||
/// <summary>重写添删改</summary>
|
||
protected virtual void BuildOverride()
|
||
{
|
||
WriteLine("///// <summary>已重载。基类先调用Valid(true)验证数据,然后在事务保护内调用OnInsert</summary>");
|
||
WriteLine("///// <returns></returns>");
|
||
WriteLine("//public override Int32 Insert()");
|
||
WriteLine("//{");
|
||
WriteLine("// return base.Insert();");
|
||
WriteLine("//}");
|
||
WriteLine();
|
||
WriteLine("///// <summary>已重载。在事务保护范围内处理业务,位于Valid之后</summary>");
|
||
WriteLine("///// <returns></returns>");
|
||
WriteLine("//protected override Int32 OnDelete()");
|
||
WriteLine("//{");
|
||
WriteLine("// return base.OnDelete();");
|
||
WriteLine("//}");
|
||
}
|
||
|
||
/// <summary>扩展属性</summary>
|
||
protected virtual void BuildExtendProperty()
|
||
{
|
||
WriteLine("#region 扩展属性");
|
||
|
||
foreach (var dc in Table.Columns)
|
||
{
|
||
// 找到名字映射
|
||
var dt = AllTables.FirstOrDefault(e => e.PrimaryKeys.Length == 1 && e.PrimaryKeys[0].DataType == dc.DataType && (e.Name + e.PrimaryKeys[0].Name).EqualIgnoreCase(dc.Name));
|
||
if (dt != null)
|
||
{
|
||
// 属性名
|
||
var pname = dt.Name;
|
||
|
||
// 备注
|
||
var dis = dc.DisplayName;
|
||
if (dis.IsNullOrEmpty()) dis = dt.DisplayName;
|
||
|
||
var pk = dt.PrimaryKeys[0];
|
||
|
||
WriteLine("/// <summary>{0}</summary>", dis);
|
||
WriteLine("[XmlIgnore, IgnoreDataMember]");
|
||
WriteLine("//[ScriptIgnore]");
|
||
WriteLine("public {1} {1} => Extends.Get({0}, k => {1}.FindBy{3}({2}));", NameOf(pname), dt.Name, dc.Name, pk.Name);
|
||
|
||
// 主字段
|
||
var master = dt.Master ?? dt.GetColumn("Name");
|
||
// 扩展属性有可能恰巧跟已有字段同名
|
||
if (master != null && !dt.Columns.Any(e => e.Name.EqualIgnoreCase(pname + master.Name)))
|
||
{
|
||
WriteLine();
|
||
WriteLine("/// <summary>{0}</summary>", dis);
|
||
WriteLine("[XmlIgnore, IgnoreDataMember]");
|
||
WriteLine("//[ScriptIgnore]");
|
||
if (!dis.IsNullOrEmpty()) WriteLine("[DisplayName(\"{0}\")]", dis);
|
||
WriteLine("[Map(nameof({0}), typeof({1}), \"{2}\")]", dc.Name, dt.Name, pk.Name);
|
||
if (master.DataType == typeof(String))
|
||
WriteLine("public {2} {0}{1} => {0}?.{1};", pname, master.Name, master.DataType.Name);
|
||
else
|
||
WriteLine("public {2} {0}{1} => {0} != null ? {0}.{1} : 0;", pname, master.Name, master.DataType.Name);
|
||
}
|
||
}
|
||
}
|
||
|
||
WriteLine("#endregion");
|
||
}
|
||
|
||
/// <summary>扩展查询</summary>
|
||
protected virtual void BuildExtendSearch()
|
||
{
|
||
WriteLine("#region 扩展查询");
|
||
|
||
// 主键
|
||
IDataColumn pk = null;
|
||
if (Table.PrimaryKeys.Length == 1)
|
||
{
|
||
pk = Table.PrimaryKeys[0];
|
||
var name = pk.CamelName();
|
||
|
||
WriteLine("/// <summary>根据{0}查找</summary>", pk.DisplayName);
|
||
WriteLine("/// <param name=\"{0}\">{1}</param>", name, pk.DisplayName);
|
||
WriteLine("/// <returns>实体对象</returns>");
|
||
WriteLine("public static {3} FindBy{0}({1} {2})", pk.Name, pk.DataType.Name, name, GenericType ? "TEntity" : Table.Name);
|
||
WriteLine("{");
|
||
{
|
||
if (pk.DataType.IsInt())
|
||
WriteLine("if ({0} <= 0) return null;", name);
|
||
else if (pk.DataType == typeof(String))
|
||
WriteLine("if ({0}.IsNullOrEmpty()) return null;", name);
|
||
|
||
WriteLine();
|
||
WriteLine("// 实体缓存");
|
||
WriteLine("if (Meta.Session.Count < 1000) return Meta.Cache.Find(e => e.{0} == {1});", pk.Name, name);
|
||
|
||
WriteLine();
|
||
WriteLine("// 单对象缓存");
|
||
WriteLine("return Meta.SingleCache[{0}];", name);
|
||
|
||
WriteLine();
|
||
WriteLine("//return Find(_.{0} == {1});", pk.Name, name);
|
||
}
|
||
WriteLine("}");
|
||
}
|
||
|
||
// 索引
|
||
foreach (var di in Table.Indexes)
|
||
{
|
||
// 跳过主键
|
||
if (di.Columns.Length == 1 && pk != null && di.Columns[0].EqualIgnoreCase(pk.Name, pk.ColumnName)) continue;
|
||
|
||
var cs = Table.GetColumns(di.Columns);
|
||
if (cs == null || cs.Length != di.Columns.Length) continue;
|
||
|
||
// 只有整数和字符串能生成查询函数
|
||
if (!cs.All(e => e.DataType.IsInt() || e.DataType == typeof(String))) continue;
|
||
|
||
WriteLine();
|
||
WriteLine("/// <summary>根据{0}查找</summary>", cs.Select(e => e.DisplayName).Join("、"));
|
||
foreach (var dc in cs)
|
||
{
|
||
WriteLine("/// <param name=\"{0}\">{1}</param>", dc.CamelName(), dc.DisplayName);
|
||
}
|
||
|
||
// 返回类型
|
||
var rt = GenericType ? "TEntity" : Table.Name;
|
||
if (!di.Unique) rt = "IList<{0}>".F(rt);
|
||
|
||
WriteLine("/// <returns>{0}</returns>", di.Unique ? "实体对象" : "实体列表");
|
||
WriteLine("public static {2} Find{3}By{0}({1})", cs.Select(e => e.Name).Join("And"), cs.Select(e => e.DataType.Name + " " + e.CamelName()).Join(", "), rt, di.Unique ? "" : "All");
|
||
WriteLine("{");
|
||
{
|
||
var exp = new StringBuilder();
|
||
var wh = new StringBuilder();
|
||
foreach (var dc in cs)
|
||
{
|
||
if (exp.Length > 0) exp.Append(" & ");
|
||
exp.AppendFormat("_.{0} == {1}", dc.Name, dc.CamelName());
|
||
|
||
if (wh.Length > 0) wh.Append(" && ");
|
||
wh.AppendFormat("e.{0} == {1}", dc.Name, dc.CamelName());
|
||
}
|
||
|
||
if (di.Unique)
|
||
{
|
||
WriteLine("// 实体缓存");
|
||
WriteLine("if (Meta.Session.Count < 1000) return Meta.Cache.Find(e => {0});", wh);
|
||
|
||
// 单对象缓存
|
||
if (cs.Length == 1 && cs[0].Master)
|
||
{
|
||
WriteLine();
|
||
WriteLine("// 单对象缓存");
|
||
WriteLine("//return Meta.SingleCache.GetItemWithSlaveKey({0}) as {1};", cs[0].CamelName(), rt);
|
||
}
|
||
|
||
WriteLine();
|
||
WriteLine("return Find({0});", exp);
|
||
}
|
||
else
|
||
{
|
||
WriteLine("// 实体缓存");
|
||
WriteLine("if (Meta.Session.Count < 1000) return Meta.Cache.FindAll(e => {0});", wh);
|
||
|
||
WriteLine();
|
||
WriteLine("return FindAll({0});", exp);
|
||
}
|
||
}
|
||
WriteLine("}");
|
||
}
|
||
|
||
WriteLine("#endregion");
|
||
}
|
||
|
||
/// <summary>高级查询</summary>
|
||
protected virtual void BuildSearch()
|
||
{
|
||
// 收集索引信息,索引中的所有字段都参与,构造一个高级查询模板
|
||
var idx = Table.Indexes ?? new List<IDataIndex>();
|
||
var cs = new List<IDataColumn>();
|
||
if (idx != null && idx.Count > 0)
|
||
{
|
||
// 索引中的所有字段,按照表字段顺序
|
||
var dcs = idx.SelectMany(e => e.Columns).Distinct().ToArray();
|
||
foreach (var dc in Table.Columns)
|
||
{
|
||
// 主键和自增,不参与
|
||
if (dc.PrimaryKey || dc.Identity) continue;
|
||
|
||
if (dc.Name.EqualIgnoreCase(dcs) || dc.ColumnName.EqualIgnoreCase(dcs)) cs.Add(dc);
|
||
}
|
||
}
|
||
|
||
var returnName = GenericType ? "TEntity" : Table.Name;
|
||
|
||
WriteLine("#region 高级查询");
|
||
if (cs.Count > 0)
|
||
{
|
||
// 时间字段。无差别支持UpdateTime/CreateTime
|
||
var dcTime = cs.FirstOrDefault(e => e.DataType == typeof(DateTime));
|
||
if (dcTime == null) dcTime = Table.GetColumns(new[] { "UpdateTime", "CreateTime" })?.FirstOrDefault();
|
||
cs.Remove(dcTime);
|
||
|
||
// 可用于关键字模糊搜索的字段
|
||
var keys = Table.Columns.Where(e => e.DataType == typeof(String) && !cs.Contains(e)).ToList();
|
||
|
||
// 注释部分
|
||
WriteLine("/// <summary>高级查询</summary>");
|
||
foreach (var dc in cs)
|
||
{
|
||
WriteLine("/// <param name=\"{0}\">{1}</param>", dc.CamelName(), dc.Description);
|
||
}
|
||
if (dcTime != null)
|
||
{
|
||
WriteLine("/// <param name=\"start\">{0}开始</param>", dcTime.DisplayName);
|
||
WriteLine("/// <param name=\"end\">{0}结束</param>", dcTime.DisplayName);
|
||
}
|
||
WriteLine("/// <param name=\"key\">关键字</param>");
|
||
WriteLine("/// <param name=\"page\">分页参数信息。可携带统计和数据权限扩展查询等信息</param>");
|
||
WriteLine("/// <returns>实体列表</returns>");
|
||
|
||
// 参数部分
|
||
var pis = cs.Join(", ", dc => $"{dc.DataType.Name} {dc.CamelName()}");
|
||
var piTime = dcTime == null ? "" : "DateTime start, DateTime end, ";
|
||
WriteLine("public static IList<{0}> Search({1}, {2}String key, PageParameter page)", returnName, pis, piTime);
|
||
WriteLine("{");
|
||
{
|
||
WriteLine("var exp = new WhereExpression();");
|
||
|
||
// 构造表达式
|
||
WriteLine();
|
||
foreach (var dc in cs)
|
||
{
|
||
if (dc.DataType.IsInt())
|
||
WriteLine("if ({0} >= 0) exp &= _.{1} == {0};", dc.CamelName(), dc.Name);
|
||
else if (dc.DataType == typeof(Boolean))
|
||
WriteLine("if ({0} != null) exp &= _.{1} == {0};", dc.CamelName(), dc.Name);
|
||
else if (dc.DataType == typeof(String))
|
||
WriteLine("if (!{0}.IsNullOrEmpty()) exp &= _.{1} == {0};", dc.CamelName(), dc.Name);
|
||
}
|
||
if (dcTime != null)
|
||
{
|
||
WriteLine("exp &= _.{0}.Between(start, end);", dcTime.Name);
|
||
}
|
||
if (keys.Count > 0)
|
||
{
|
||
WriteLine("if (!key.IsNullOrEmpty()) exp &= {0};", keys.Join(" | ", k => $"_.{k.Name}.Contains(key)"));
|
||
}
|
||
|
||
// 查询返回
|
||
WriteLine();
|
||
WriteLine("return FindAll(exp, page);");
|
||
}
|
||
WriteLine("}");
|
||
|
||
}
|
||
|
||
// 字段缓存,用于魔方前台下拉选择
|
||
{
|
||
// 主键和时间字段
|
||
var pk = Table.Columns.FirstOrDefault(e => e.Identity);
|
||
var pname = pk?.Name ?? "Id";
|
||
var dcTime = cs.FirstOrDefault(e => e.DataType == typeof(DateTime));
|
||
var tname = dcTime?.Name ?? "CreateTime";
|
||
|
||
// 遍历索引,第一个字段是字符串类型,则为其生成下拉选择
|
||
var count = 0;
|
||
foreach (var di in idx)
|
||
{
|
||
if (di.Columns == null || di.Columns.Length == 0) continue;
|
||
|
||
// 单字段唯一索引,不需要
|
||
if (di.Unique && di.Columns.Length == 1) continue;
|
||
|
||
var dc = Table.GetColumn(di.Columns[0]);
|
||
if (dc == null || dc.DataType != typeof(String) || dc.Master) continue;
|
||
|
||
var name = dc.Name;
|
||
|
||
WriteLine();
|
||
WriteLine($"// Select Count({pname}) as {pname},{name} From {Table.Name} Where {tname}>'2020-01-24 00:00:00' Group By {name} Order By {pname} Desc limit 20");
|
||
WriteLine($"static readonly FieldCache<{returnName}> _{name}Cache = new FieldCache<{returnName}>(nameof({name}))");
|
||
WriteLine("{");
|
||
{
|
||
WriteLine($"//Where = _.{tname} > DateTime.Today.AddDays(-30) & Expression.Empty");
|
||
}
|
||
WriteLine("};");
|
||
WriteLine();
|
||
WriteLine($"/// <summary>获取{dc.DisplayName}列表,字段缓存10分钟,分组统计数据最多的前20种,用于魔方前台下拉选择</summary>");
|
||
WriteLine("/// <returns></returns>");
|
||
WriteLine($"public static IDictionary<String, String> Get{name}List() => _{name}Cache.FindAllName();");
|
||
|
||
count++;
|
||
}
|
||
|
||
// 如果没有输出,则生成一个注释的模板
|
||
if (count == 0)
|
||
{
|
||
WriteLine();
|
||
WriteLine($"// Select Count({pname}) as {pname},Category From {Table.Name} Where {tname}>'2020-01-24 00:00:00' Group By Category Order By {pname} Desc limit 20");
|
||
WriteLine($"//static readonly FieldCache<{returnName}> _CategoryCache = new FieldCache<{returnName}>(nameof(Category))");
|
||
WriteLine("//{");
|
||
{
|
||
WriteLine($"//Where = _.{tname} > DateTime.Today.AddDays(-30) & Expression.Empty");
|
||
}
|
||
WriteLine("//};");
|
||
WriteLine();
|
||
WriteLine("///// <summary>获取类别列表,字段缓存10分钟,分组统计数据最多的前20种,用于魔方前台下拉选择</summary>");
|
||
WriteLine("///// <returns></returns>");
|
||
WriteLine("//public static IDictionary<String, String> GetCategoryList() => _CategoryCache.FindAllName();");
|
||
}
|
||
}
|
||
|
||
WriteLine("#endregion");
|
||
}
|
||
|
||
/// <summary>业务操作</summary>
|
||
protected virtual void BuildBusiness()
|
||
{
|
||
WriteLine("#region 业务操作");
|
||
WriteLine("#endregion");
|
||
}
|
||
#endregion
|
||
}
|
||
} |