2034 lines
79 KiB
C#
2034 lines
79 KiB
C#
using System.Reflection;
|
||
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>业务类</summary>
|
||
public Boolean Business { get; set; }
|
||
|
||
/// <summary>合并业务类,当业务类已存在时。默认true</summary>
|
||
public Boolean MergeBusiness { get; set; } = true;
|
||
|
||
/// <summary>使用缓存。默认true,标记为大数据的表不使用缓存</summary>
|
||
public Boolean UsingCache { get; set; } = true;
|
||
|
||
/// <summary>数据规模字段。标识是否大数据表</summary>
|
||
public IDataColumn? ScaleColumn { get; set; }
|
||
|
||
/// <summary>所有表类型名。用于扩展属性</summary>
|
||
public IList<IDataTable> AllTables { get; set; } = [];
|
||
|
||
/// <summary>实体类生成选型</summary>
|
||
public EntityBuilderOption EntityOption => (EntityBuilderOption)Option;
|
||
|
||
/// <summary>成员集合。主要用于避免重复生成相同签名的成员,方法的签名包括参数列表</summary>
|
||
public IList<String> Members { get; set; } = [];
|
||
#endregion 属性
|
||
|
||
#region 静态快速
|
||
|
||
/// <summary>修正模型文件</summary>
|
||
/// <param name="xmlFile"></param>
|
||
/// <param name="option"></param>
|
||
/// <param name="atts"></param>
|
||
/// <param name="tables"></param>
|
||
/// <param name="log"></param>
|
||
public static void FixModelFile(String xmlFile, BuilderOption option, IDictionary<String, String> atts, IList<IDataTable> tables, ILog? log = null)
|
||
{
|
||
// 保存文件名
|
||
if (xmlFile.IsNullOrEmpty()) xmlFile = atts["ModelFile"];
|
||
|
||
// 给默认字段赋值
|
||
var def = option.GetType().CreateInstance() as BuilderOption;
|
||
foreach (var pi in option.GetType().GetProperties(true))
|
||
{
|
||
var val = option.GetValue(pi);
|
||
if (pi.PropertyType == typeof(String) && val is String str)
|
||
{
|
||
if (str.IsNullOrEmpty()) option.SetValue(pi, def.GetValue(pi));
|
||
}
|
||
else
|
||
{
|
||
if (val == null) option.SetValue(pi, def.GetValue(pi));
|
||
}
|
||
}
|
||
|
||
// 反哺。确保输出空特性
|
||
//atts["Output"] = option.Output + "";
|
||
//atts["NameSpace"] = option.Namespace + "";
|
||
//atts["ConnName"] = option.ConnName + "";
|
||
//atts["DisplayName"] = option.DisplayName + "";
|
||
//atts["BaseClass"] = option.BaseClass + "";
|
||
|
||
// 生成决定是否生成魔方代码
|
||
//atts["CubeOutput"] = option.Items?["CubeOutput"];
|
||
//atts["CubeProject"] = option.Items?["CubeProject"];
|
||
|
||
// 清理不再使用的历史配置项
|
||
atts.Remove("NameIgnoreCase");
|
||
atts.Remove("IgnoreNameCase");
|
||
//atts.Remove("ChineseFileName");
|
||
atts.Remove("ModelFile");
|
||
atts.Remove("RenderGenEntity");
|
||
|
||
foreach (var item in tables)
|
||
{
|
||
item.Properties.Remove("RenderGenEntity");
|
||
}
|
||
|
||
// 格式化处理字段名
|
||
//if (Enum.TryParse<NameFormats>(atts["NameFormat"], true, out var format) && format > NameFormats.Default)
|
||
if (option is EntityBuilderOption opt && opt.NameFormat > NameFormats.Default)
|
||
{
|
||
log?.Info("处理表名字段名为:{0}", opt.NameFormat);
|
||
|
||
var resolve = ModelResolver.Current;
|
||
foreach (var dt in tables)
|
||
{
|
||
if (dt.TableName.IsNullOrEmpty() || dt.TableName == dt.Name)
|
||
dt.TableName = resolve.GetDbName(dt.Name, opt.NameFormat);
|
||
|
||
foreach (var col in dt.Columns)
|
||
{
|
||
if (col.ColumnName.IsNullOrEmpty() || col.ColumnName == col.Name)
|
||
col.ColumnName = resolve.GetDbName(col.Name, opt.NameFormat);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 雪花Id主键,默认设置数据规模DataScale
|
||
foreach (var table in tables)
|
||
{
|
||
if (table.Columns.Any(e => !e.DataScale.IsNullOrEmpty())) continue;
|
||
|
||
// 雪花Id主键,默认设置数据规模DataScale
|
||
if (table.PrimaryKeys.Length == 1)
|
||
{
|
||
var column = table.PrimaryKeys[0];
|
||
if (!column.Identity && column.DataType == typeof(Int64))
|
||
{
|
||
column.DataScale = "time";
|
||
continue;
|
||
}
|
||
}
|
||
|
||
// 只读日志表
|
||
if (table.InsertOnly)
|
||
{
|
||
// 第一个时间日期索引字段
|
||
IDataColumn? column = null;
|
||
foreach (var di in table.Indexes.OrderBy(e => e.Columns.Length).OrderByDescending(e => e.Unique).ThenByDescending(e => e.PrimaryKey))
|
||
{
|
||
if (di.Columns == null || di.Columns.Length == 0) continue;
|
||
|
||
var col = table.GetColumn(di.Columns[0]);
|
||
if (col != null && col.DataType == typeof(DateTime))
|
||
{
|
||
column = col;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (column != null && column.DataType == typeof(DateTime))
|
||
{
|
||
column.DataScale = "time";
|
||
continue;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 使用缓存
|
||
foreach (var table in tables)
|
||
{
|
||
var cache = table.Properties["UsingCache"];
|
||
if (!cache.IsNullOrEmpty() && !cache.ToBoolean(true))
|
||
table.Properties["UsingCache"] = false.ToString();
|
||
else
|
||
table.Properties.Remove("UsingCache");
|
||
}
|
||
|
||
// 更新xsd
|
||
atts["xmlns"] = "https://newlifex.com/Model202407.xsd";
|
||
atts["xs:schemaLocation"] = "https://newlifex.com https://newlifex.com/Model202407.xsd";
|
||
|
||
// 版本和教程
|
||
//var asm = AssemblyX.Create(Assembly.GetExecutingAssembly());
|
||
//atts["Version"] = asm.FileVersion + "";
|
||
atts["Document"] = "https://newlifex.com/xcode/model";
|
||
//if (option is EntityBuilderOption opt2)
|
||
//{
|
||
// opt2.Version = asm.FileVersion + "";
|
||
// opt2.Document = "https://newlifex.com/xcode/model";
|
||
//}
|
||
|
||
// 保存模型文件
|
||
var xmlContent = File.ReadAllText(xmlFile);
|
||
var xml2 = ModelHelper.ToXml(tables, option, atts);
|
||
if (xmlContent != xml2)
|
||
{
|
||
log?.Info("修正模型:{0}", xmlFile);
|
||
|
||
File.WriteAllText(xmlFile, xml2, Encoding.UTF8);
|
||
}
|
||
}
|
||
|
||
/// <summary>为Xml模型文件生成实体类</summary>
|
||
/// <param name="tables">模型文件</param>
|
||
/// <param name="option">生成可选项</param>
|
||
/// <param name="log"></param>
|
||
public static Int32 BuildTables(IList<IDataTable> tables, EntityBuilderOption option, ILog? log = null)
|
||
{
|
||
if (tables == null || tables.Count == 0) return 0;
|
||
|
||
if (option == null)
|
||
option = new EntityBuilderOption();
|
||
else
|
||
option = (option.Clone() as EntityBuilderOption)!;
|
||
//option.Partial = true;
|
||
|
||
var output = option.Output;
|
||
if (output.IsNullOrEmpty()) output = ".";
|
||
log?.Info("生成实体类 {0}", output.GetBasePath());
|
||
|
||
var displayNames = new HashSet<String>();
|
||
|
||
var count = 0;
|
||
foreach (var item in tables)
|
||
{
|
||
// 跳过排除项
|
||
if (option.Excludes.Contains(item.Name)) continue;
|
||
if (option.Excludes.Contains(item.TableName)) continue;
|
||
|
||
var builder = new EntityBuilder
|
||
{
|
||
AllTables = tables,
|
||
Option = option.Clone(),
|
||
Log = log!
|
||
};
|
||
|
||
// 不能对option赋值,否则所有table的ModelNameForToModel就相同了
|
||
//if (option.ModelNameForToModel.IsNullOrEmpty())
|
||
//{
|
||
// option.ModelNameForToModel = item.Name;
|
||
//}
|
||
if (builder.EntityOption.ModelNameForToModel.IsNullOrEmpty())
|
||
{
|
||
builder.EntityOption.ModelNameForToModel = item.Name;
|
||
}
|
||
|
||
// 如果已存在重复中文名,则使用英文名
|
||
var chinese = option.ChineseFileName;
|
||
if (chinese && !item.DisplayName.IsNullOrEmpty())
|
||
{
|
||
if (displayNames.Contains(item.DisplayName))
|
||
chinese = false;
|
||
else
|
||
displayNames.Add(item.DisplayName);
|
||
}
|
||
|
||
builder.Load(item);
|
||
|
||
builder.Execute();
|
||
builder.Save(null, true, chinese);
|
||
|
||
builder.Clear();
|
||
builder.Business = true;
|
||
builder.Execute();
|
||
builder.Save(null, false, chinese);
|
||
}
|
||
|
||
return count;
|
||
}
|
||
|
||
#endregion 静态快速
|
||
|
||
#region 方法
|
||
|
||
/// <summary>加载数据表</summary>
|
||
/// <param name="table"></param>
|
||
public override void Load(IDataTable table)
|
||
{
|
||
Table = table;
|
||
|
||
var option = EntityOption;
|
||
|
||
base.Load(table);
|
||
|
||
// 连接名
|
||
var connName = table.ConnName;
|
||
if (!connName.IsNullOrEmpty()) option.ConnName = connName;
|
||
|
||
// 基类
|
||
var str = table.Properties["BaseClass"];
|
||
if (!str.IsNullOrEmpty()) option.BaseClass = str;
|
||
|
||
// Copy模版
|
||
var modelClass = table.Properties["ModelClass"];
|
||
var modelInterface = table.Properties["ModelInterface"];
|
||
if (!modelInterface.IsNullOrEmpty())
|
||
{
|
||
option.BaseClass = modelInterface;
|
||
option.ModelNameForCopy = modelInterface;
|
||
}
|
||
else if (!modelClass.IsNullOrEmpty())
|
||
option.ModelNameForCopy = modelClass;
|
||
|
||
// 使用缓存
|
||
if (UsingCache)
|
||
{
|
||
var cache = table.Properties["UsingCache"];
|
||
if (!cache.IsNullOrEmpty() && !cache.ToBoolean(true)) UsingCache = false;
|
||
}
|
||
|
||
if (UsingCache)
|
||
{
|
||
// 标记为大数据的表不使用缓存
|
||
var fi = table.Columns.FirstOrDefault(e => e.DataScale.EqualIgnoreCase("time") || e.DataScale.StartsWithIgnoreCase("time:", "timeShard:"));
|
||
if (fi != null) UsingCache = false;
|
||
}
|
||
}
|
||
|
||
#endregion 方法
|
||
|
||
#region 基础
|
||
/// <summary>生成前的准备工作。计算类型以及命名空间等</summary>
|
||
protected override void Prepare()
|
||
{
|
||
// 标记为大数据的表不使用缓存
|
||
var column = Table.Columns.FirstOrDefault(e => e.DataScale.EqualIgnoreCase("time") || e.DataScale.StartsWithIgnoreCase("time:", "timeShard:"));
|
||
if (column != null)
|
||
{
|
||
UsingCache = false;
|
||
ScaleColumn = column;
|
||
|
||
if (column.DataScale.StartsWithIgnoreCase("timeShard:"))
|
||
{
|
||
var us = Option.Usings;
|
||
us.Add("System.Linq");
|
||
us.Add("NewLife.Log");
|
||
}
|
||
}
|
||
|
||
// 增加常用命名空间
|
||
AddNameSpace();
|
||
|
||
base.Prepare();
|
||
|
||
if (!Business)
|
||
{
|
||
// 读取biz文件,识别其中已生成的扩展查询方法,避免在数据类中重复生成
|
||
var codeFile = GetFileName(".Biz.cs", Option.ChineseFileName);
|
||
LoadCodeFile(codeFile);
|
||
}
|
||
else
|
||
{
|
||
var codeFile = GetFileName(".cs", Option.ChineseFileName);
|
||
LoadCodeFile(codeFile);
|
||
}
|
||
}
|
||
|
||
/// <summary>加载代码文件,读取其中的方法作为成员,避免重复生成</summary>
|
||
/// <param name="file"></param>
|
||
public void LoadCodeFile(String file)
|
||
{
|
||
if (!file.IsNullOrEmpty() && File.Exists(file))
|
||
{
|
||
var txt = File.ReadAllText(file);
|
||
if (!txt.IsNullOrEmpty())
|
||
{
|
||
var sections = MemberSection.GetMethods(txt);
|
||
foreach (var sec in sections)
|
||
{
|
||
Members.Add(sec.FullName);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>增加常用命名空间</summary>
|
||
protected virtual void AddNameSpace()
|
||
{
|
||
var us = Option.Usings;
|
||
|
||
us.Add("NewLife");
|
||
us.Add("NewLife.Data");
|
||
us.Add("XCode");
|
||
us.Add("XCode.Cache");
|
||
us.Add("XCode.Configuration");
|
||
us.Add("XCode.DataAccessLayer");
|
||
//us.Add("XCode.Common");
|
||
if (Business) us.Add("XCode.Shards");
|
||
|
||
if (Business)
|
||
{
|
||
//us.Add("System.ComponentModel.DataAnnotations");//属性验证
|
||
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.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");
|
||
us.Add("XCode.Shards");
|
||
}
|
||
}
|
||
|
||
/// <summary>清空上下文。便于重新生成</summary>
|
||
public override void Clear()
|
||
{
|
||
base.Clear();
|
||
|
||
Members.Clear();
|
||
}
|
||
|
||
/// <summary>获取基类</summary>
|
||
/// <returns></returns>
|
||
protected override String? GetBaseClass()
|
||
{
|
||
var baseClass = Option.BaseClass;
|
||
//if (Option.HasIModel)
|
||
//{
|
||
// if (!baseClass.IsNullOrEmpty()) baseClass += ", ";
|
||
// baseClass += "IModel";
|
||
//}
|
||
|
||
var bs = baseClass?.Split(',').Select(e => e.Trim()).ToList() ?? [];
|
||
|
||
// 数据类的基类只有接口,业务类基类则比较复杂
|
||
var name = "";
|
||
if (Business)
|
||
{
|
||
// 数据类只要实体基类
|
||
if (bs.Count >= 2)
|
||
name = bs[0];
|
||
if (name.IsNullOrEmpty()) name = "Entity";
|
||
|
||
name = $"{name}<{ClassName}>";
|
||
}
|
||
else
|
||
{
|
||
if (bs.Count >= 2)
|
||
bs.RemoveAt(0); // 移除第一个,因为第一个是实体基类
|
||
|
||
// 有可能实现了接口拷贝
|
||
var model = Option.ModelNameForCopy;
|
||
if (!model.IsNullOrEmpty())
|
||
{
|
||
if (model.StartsWith("I")) bs.Add(model);
|
||
|
||
bs.Add($"IEntity<{model}>");
|
||
}
|
||
|
||
// 数据类不要实体基类
|
||
bs = bs.Where(e => e != "Entity" && !e.StartsWithIgnoreCase("Entity<", "EntityBase<")).ToList();
|
||
if (bs.Count > 0) name = bs.Distinct().Join(", ");
|
||
}
|
||
|
||
return name?.Replace("{name}", ClassName);
|
||
}
|
||
|
||
/// <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;
|
||
}
|
||
|
||
// Biz业务文件已存在时,部分覆盖
|
||
if (Business && !overwrite && MergeBusiness)
|
||
{
|
||
var fileName = GetFileName(ext, chineseFileName);
|
||
if (File.Exists(fileName))
|
||
{
|
||
Merge(fileName);
|
||
return fileName;
|
||
}
|
||
}
|
||
|
||
return base.Save(ext, overwrite, chineseFileName); ;
|
||
}
|
||
|
||
/// <summary>合并当前生成内容到旧文件中</summary>
|
||
/// <param name="fileName"></param>
|
||
public void Merge(String fileName)
|
||
{
|
||
// 新旧代码分组
|
||
var newLines = ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None);
|
||
var oldLines = File.ReadAllLines(fileName).ToList();
|
||
|
||
var changed = 0;
|
||
|
||
// 合并扩展属性
|
||
{
|
||
var sname = "#region 扩展属性";
|
||
var newNs = CodeRange.Find(newLines, sname, "#endregion");
|
||
var oldNs = CodeRange.Find(oldLines, sname, "#endregion");
|
||
|
||
// 两个都有才合并
|
||
if (newNs != null && oldNs != null)
|
||
{
|
||
// endregion 所在行
|
||
var p = oldNs.Start + oldNs.Count - 1;
|
||
foreach (var item in newNs.Sections)
|
||
{
|
||
// 如果旧文件中不存在,则插入
|
||
if (!oldNs.Sections.Any(e => e.Name == item.Name))
|
||
{
|
||
// 前面有变化,需要插入空行
|
||
if (changed > 0 || oldNs.Sections.Count > 0) oldLines.Insert(p++, "");
|
||
|
||
foreach (var elm in item.Lines)
|
||
{
|
||
oldLines.Insert(p++, elm);
|
||
}
|
||
changed++;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 合并扩展查询
|
||
{
|
||
var sname = "#region 扩展查询";
|
||
var newNs = CodeRange.Find(newLines, sname, "#endregion");
|
||
var oldNs = CodeRange.Find(oldLines, sname, "#endregion");
|
||
|
||
// 两个都有才合并
|
||
if (newNs != null && oldNs != null)
|
||
{
|
||
// endregion 所在行
|
||
var p = oldNs.Start + oldNs.Count - 1;
|
||
foreach (var item in newNs.Sections)
|
||
{
|
||
// 如果旧文件中不存在,则插入
|
||
//if (!oldNs.Sections.Any(e => e.Name == item.Name))
|
||
// 可能参数名大小写不一致
|
||
if (!oldNs.Sections.Any(e => e.Name.EqualIgnoreCase(item.Name)))
|
||
{
|
||
// 前面有变化,需要插入空行
|
||
if (changed > 0 || oldNs.Sections.Count > 0) oldLines.Insert(p++, "");
|
||
|
||
foreach (var elm in item.Lines)
|
||
{
|
||
oldLines.Insert(p++, elm);
|
||
}
|
||
changed++;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (changed > 0) File.WriteAllText(fileName, oldLines.Join(Environment.NewLine).Trim());
|
||
}
|
||
|
||
/// <summary>生成尾部</summary>
|
||
protected override void OnExecuted()
|
||
{
|
||
// 类接口
|
||
WriteLine("}");
|
||
|
||
//if (!Business)
|
||
//{
|
||
// WriteLine();
|
||
// BuildInterface();
|
||
//}
|
||
|
||
//if (!Option.Namespace.IsNullOrEmpty())
|
||
//{
|
||
// Writer.Write("}");
|
||
//}
|
||
}
|
||
|
||
/// <summary>生成主体</summary>
|
||
protected override void BuildItems()
|
||
{
|
||
if (Business)
|
||
{
|
||
BuildAction();
|
||
|
||
WriteLine();
|
||
BuildExtendProperty();
|
||
|
||
//WriteLine();
|
||
//BuildExtendSearch();
|
||
|
||
WriteLine();
|
||
BuildSearch();
|
||
|
||
WriteLine();
|
||
BuildBusiness();
|
||
}
|
||
else
|
||
{
|
||
base.BuildItems();
|
||
|
||
// 生成拷贝函数。需要有基类
|
||
//var bs = Option.BaseClass.Split(",").Select(e => e.Trim()).ToArray();
|
||
//var model = bs.FirstOrDefault(e => e[0] == 'I' && e.Contains("{name}"));
|
||
var model = Option.ModelNameForCopy;
|
||
if (!model.IsNullOrEmpty())
|
||
{
|
||
WriteLine();
|
||
BuildCopy(model.Replace("{name}", Table.Name));
|
||
}
|
||
|
||
WriteLine();
|
||
BuildIndexItems();
|
||
|
||
WriteLine();
|
||
BuildMap();
|
||
|
||
WriteLine();
|
||
BuildExtendSearch();
|
||
|
||
//todo 条件暂时未成熟,生成的高级查询有些缺陷,需要人工介入修改
|
||
//BuildAdvanceSearch();
|
||
|
||
if (ScaleColumn != null)
|
||
{
|
||
WriteLine();
|
||
BuildDelete(ScaleColumn);
|
||
}
|
||
|
||
WriteLine();
|
||
BuildFieldName();
|
||
}
|
||
}
|
||
|
||
#endregion 基础
|
||
|
||
#region 数据类
|
||
|
||
/// <summary>实体类头部</summary>
|
||
protected override void BuildAttribute()
|
||
{
|
||
if (Business)
|
||
{
|
||
//WriteLine("/// <summary>{0}</summary>", Table.Description);
|
||
return;
|
||
}
|
||
|
||
// 注释
|
||
var des = Table.Description;
|
||
if (!Option.DisplayNameTemplate.IsNullOrEmpty())
|
||
{
|
||
var dis = Table.DisplayName;
|
||
if (!dis.IsNullOrEmpty())
|
||
des = des?.TrimStart(dis, "。");
|
||
des = Option.DisplayNameTemplate.Replace("{displayName}", Table.DisplayName) + "。" + des;
|
||
}
|
||
WriteLine("/// <summary>{0}</summary>", des);
|
||
WriteLine("[Serializable]");
|
||
WriteLine("[DataObject]");
|
||
|
||
if (!des.IsNullOrEmpty()) WriteLine("[Description(\"{0}\")]", des);
|
||
|
||
var dt = Table;
|
||
foreach (var item in dt.Indexes)
|
||
{
|
||
WriteLine("[BindIndex(\"{0}\", {1}, \"{2}\")]", item.Name, item.Unique.ToString().ToLower(), item.Columns.Join());
|
||
}
|
||
|
||
var connName = dt.Properties["ConnName"];
|
||
if (connName.IsNullOrEmpty()) connName = EntityOption.ConnName;
|
||
WriteLine("[BindTable(\"{0}\", Description = \"{1}\", ConnName = \"{2}\", DbType = DatabaseType.{3})]", dt.TableName, dt.Description, connName, 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;
|
||
if (type == "String" && Option.Nullable && column.Nullable) type = "String?";
|
||
|
||
// 字段
|
||
if (type == "String" && Option.Nullable)
|
||
{
|
||
if (column.Nullable)
|
||
WriteLine("private {0} _{1};", type, dc.Name);
|
||
else
|
||
WriteLine("private {0} _{1} = null!;", type, dc.Name);
|
||
}
|
||
else
|
||
WriteLine("private {0} _{1};", type, dc.Name);
|
||
|
||
// 注释
|
||
var des = dc.Description;
|
||
WriteLine("/// <summary>{0}</summary>", des);
|
||
|
||
// 附加特性
|
||
if (dc.Properties.TryGetValue("Attribute", out var att))
|
||
{
|
||
// 兼容支持新旧两种格式
|
||
var str = att.Replace("{name}", dc.Name);
|
||
if (str[0] != '[')
|
||
WriteLine("[{0}]", str);
|
||
else
|
||
WriteLine("{0}", str);//lps 2023-07-22 去掉两边的方括号,以便支持多个验证。例如:<Column Name="TestQuantity" DataType="Int32" Description="测试数量" Attribute="[Required(ErrorMessage = "{0}必须填写")][Range(1, 100,ErrorMessage ="超出范围")]" />
|
||
}
|
||
|
||
// 分类特性
|
||
if (dc.Properties.TryGetValue("Category", out att) && !att.IsNullOrEmpty())
|
||
WriteLine("[Category(\"{0}\")]", att);
|
||
|
||
//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);
|
||
|
||
var sb = Pool.StringBuilder.Get();
|
||
sb.AppendFormat("[BindColumn(\"{0}\", \"{1}\", \"{2}\"", dc.ColumnName, dc.Description, dc.RawType);
|
||
|
||
// 元素类型
|
||
if (!dc.ItemType.IsNullOrEmpty()) sb.AppendFormat(", ItemType = \"{0}\"", dc.ItemType);
|
||
|
||
// 支持生成带精度的特性
|
||
if (dc.Precision > 0 || dc.Scale > 0) sb.AppendFormat(", Precision = {0}, Scale = {1}", dc.Precision, dc.Scale);
|
||
|
||
// 默认值
|
||
if (!dc.DefaultValue.IsNullOrEmpty()) sb.AppendFormat(", DefaultValue = \"{0}\"", dc.DefaultValue);
|
||
|
||
// 数据规模
|
||
if (!dc.DataScale.IsNullOrEmpty()) sb.AppendFormat(", DataScale = \"{0}\"", dc.DataScale);
|
||
|
||
////添加自定义控件默认值
|
||
//if (!dc.ItemDefaultValue.IsNullOrEmpty()) sb.AppendFormat(", ItemDefaultValue = \"{0}\"", dc.ItemDefaultValue);
|
||
|
||
if (dc.Master) sb.Append(", Master = true");
|
||
|
||
sb.Append(")]");
|
||
|
||
WriteLine(sb.Put(true));
|
||
|
||
WriteLine("public {0} {1} {{ get => _{1}; set {{ if (OnPropertyChanging(\"{1}\", value)) {{ _{1} = value; OnPropertyChanged(\"{1}\"); }} }} }}", type, dc.Name);
|
||
}
|
||
|
||
/// <summary>生成索引访问器</summary>
|
||
protected override void BuildIndexItems()
|
||
{
|
||
WriteLine("#region 获取/设置 字段值");
|
||
WriteLine("/// <summary>获取/设置 字段值</summary>");
|
||
WriteLine("/// <param name=\"name\">字段名</param>");
|
||
WriteLine("/// <returns></returns>");
|
||
if (Option.Nullable)
|
||
WriteLine("public override Object? this[String name]");
|
||
else
|
||
WriteLine("public override Object this[String name]");
|
||
WriteLine("{");
|
||
|
||
// get
|
||
WriteLine("get => name switch");
|
||
WriteLine("{");
|
||
{
|
||
foreach (var column in Table.Columns)
|
||
{
|
||
// 跳过排除项
|
||
if (Option.Excludes.Contains(column.Name)) continue;
|
||
if (Option.Excludes.Contains(column.ColumnName)) continue;
|
||
|
||
WriteLine("\"{0}\" => _{0},", column.Name);
|
||
}
|
||
//WriteLine("default: return base[name];");
|
||
WriteLine("_ => base[name]");
|
||
}
|
||
WriteLine("};");
|
||
|
||
// set
|
||
WriteLine("set");
|
||
WriteLine("{");
|
||
{
|
||
WriteLine("switch (name)");
|
||
WriteLine("{");
|
||
var conv = typeof(Convert);
|
||
foreach (var column in Table.Columns)
|
||
{
|
||
// 跳过排除项
|
||
if (Option.Excludes.Contains(column.Name)) continue;
|
||
if (Option.Excludes.Contains(column.ColumnName)) continue;
|
||
|
||
var type = column.Properties["Type"];
|
||
if (type.IsNullOrEmpty()) type = column.DataType?.Name;
|
||
|
||
if (!type.IsNullOrEmpty())
|
||
{
|
||
if (!type.Contains(".") && conv.GetMethod("To" + type, [typeof(Object)]) != null)
|
||
{
|
||
switch (type)
|
||
{
|
||
case "Int32":
|
||
WriteLine("case \"{0}\": _{0} = value.ToInt(); break;", column.Name);
|
||
break;
|
||
|
||
case "Int64":
|
||
WriteLine("case \"{0}\": _{0} = value.ToLong(); break;", column.Name);
|
||
break;
|
||
|
||
case "Double":
|
||
WriteLine("case \"{0}\": _{0} = value.ToDouble(); break;", column.Name);
|
||
break;
|
||
|
||
case "Boolean":
|
||
WriteLine("case \"{0}\": _{0} = value.ToBoolean(); break;", column.Name);
|
||
break;
|
||
|
||
case "DateTime":
|
||
WriteLine("case \"{0}\": _{0} = value.ToDateTime(); break;", column.Name);
|
||
break;
|
||
|
||
default:
|
||
WriteLine("case \"{0}\": _{0} = Convert.To{1}(value); break;", column.Name, type);
|
||
break;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
try
|
||
{
|
||
// 特殊支持枚举
|
||
if (column.DataType.IsInt())
|
||
WriteLine("case \"{0}\": _{0} = ({1})value.ToInt(); break;", column.Name, type);
|
||
else
|
||
WriteLine("case \"{0}\": _{0} = ({1})value; break;", column.Name, type);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
XTrace.WriteException(ex);
|
||
WriteLine("case \"{0}\": _{0} = ({1})value; break;", column.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 column in Table.Columns)
|
||
{
|
||
// 跳过排除项
|
||
if (Option.Excludes.Contains(column.Name)) continue;
|
||
if (Option.Excludes.Contains(column.ColumnName)) continue;
|
||
|
||
WriteLine("/// <summary>{0}</summary>", column.Description);
|
||
WriteLine("public static readonly Field {0} = FindByName(\"{0}\");", column.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 column in Table.Columns)
|
||
{
|
||
// 跳过排除项
|
||
if (Option.Excludes.Contains(column.Name)) continue;
|
||
if (Option.Excludes.Contains(column.ColumnName)) continue;
|
||
|
||
WriteLine("/// <summary>{0}</summary>", column.Description);
|
||
WriteLine("public const String {0} = \"{0}\";", column.Name);
|
||
if (--k > 0) WriteLine();
|
||
}
|
||
WriteLine("}");
|
||
|
||
WriteLine("#endregion");
|
||
}
|
||
|
||
/// <summary>扩展属性</summary>
|
||
protected virtual void BuildMap()
|
||
{
|
||
WriteLine("#region 关联映射");
|
||
|
||
foreach (var column in Table.Columns)
|
||
{
|
||
// 跳过排除项
|
||
if (Option.Excludes.Contains(column.Name)) continue;
|
||
if (Option.Excludes.Contains(column.ColumnName)) continue;
|
||
|
||
if (column.Map.IsNullOrEmpty()) continue;
|
||
|
||
// 格式:表+主键+显示字段+属性名
|
||
// Role@Id@Name
|
||
// XCode.Membership.Area@Id@Path@AreaPath
|
||
// Tenant@Id@$
|
||
// $表示用ToString()替代显示字段
|
||
var ss = column.Map.Split('@');
|
||
var fullName = ss[0];
|
||
var className = fullName;
|
||
var p = className.LastIndexOf('.');
|
||
if (p > 0) className = className[(p + 1)..];
|
||
|
||
// 找到映射表,有可能映射表在别的模型集,mapTable可能为空,此时直接使用类名
|
||
var mapTable = AllTables.FirstOrDefault(e => className.EqualIgnoreCase(e.Name, e.TableName));
|
||
//if (mapTable == null) continue;
|
||
|
||
IDataColumn? mapId = null;
|
||
if (mapTable != null)
|
||
mapId = ss.Length > 1 ? mapTable.GetColumn(ss[1]) : mapTable.PrimaryKeys.FirstOrDefault();
|
||
//if (mapId == null) continue;
|
||
var mapIdName = mapId?.Name ?? ss[1];
|
||
|
||
IDataColumn? mapName = null;
|
||
if (mapTable != null)
|
||
{
|
||
mapName = ss.Length > 2 && ss[2] != "$" ? mapTable.GetColumn(ss[2]) : null;
|
||
mapName ??= mapTable.Columns.FirstOrDefault(e => e.Master);
|
||
mapName ??= mapTable.GetColumn("Name");
|
||
}
|
||
else
|
||
{
|
||
// 默认字符串类型
|
||
mapName = new XField { Name = ss[2], DataType = typeof(String) };
|
||
}
|
||
|
||
// 属性名
|
||
var name = column.Name.TrimEnd("Id", "ID", mapIdName);
|
||
if (Table.Columns.Any(e => e.Name.EqualIgnoreCase(name))) name = "My" + name;
|
||
|
||
// 备注
|
||
var dis = column.DisplayName;
|
||
if (dis.IsNullOrEmpty()) dis = mapTable?.DisplayName;
|
||
|
||
WriteLine("/// <summary>{0}</summary>", dis);
|
||
WriteLine("[XmlIgnore, IgnoreDataMember, ScriptIgnore]");
|
||
if (Option.Nullable)
|
||
WriteLine("public {0}? {1} => Extends.Get(nameof({1}), k => {0}.FindBy{2}({3}));", fullName, name, mapIdName, column.Name);
|
||
else
|
||
WriteLine("public {0} {1} => Extends.Get(nameof({1}), k => {0}.FindBy{2}({3}));", fullName, name, mapIdName, column.Name);
|
||
|
||
var myName = ss.Length > 3 ? ss[3] : null;
|
||
if (myName.IsNullOrEmpty())
|
||
{
|
||
myName = column.Name.TrimEnd("Id", "ID", mapIdName);
|
||
if (mapName != null && mapName.Name != "$") myName += mapName.Name;
|
||
}
|
||
|
||
// 扩展属性有可能恰巧跟已有字段同名
|
||
if (!myName.IsNullOrEmpty() && !Table.Columns.Any(e => e.Name.EqualIgnoreCase(myName)))
|
||
{
|
||
var type = Option.Nullable ? "String?" : "String";
|
||
|
||
WriteLine();
|
||
WriteLine("/// <summary>{0}</summary>", dis);
|
||
WriteLine("[Map(nameof({0}), typeof({1}), \"{2}\")]", column.Name, fullName, mapIdName);
|
||
if (column.Properties.TryGetValue("Category", out var att) && !att.IsNullOrEmpty())
|
||
WriteLine("[Category(\"{0}\")]", att);
|
||
if (ss.Length > 2 && ss[2] == "$")
|
||
WriteLine("public {2} {0} => {1}?.ToString();", myName, name, type);
|
||
else if (mapName != null)
|
||
{
|
||
if (ss.Length > 2 && mapName.DataType == typeof(String))
|
||
WriteLine("public {3} {0} => {1}?.{2};", myName, name, ss[2], type);
|
||
else if (mapName.DataType == typeof(String))
|
||
WriteLine("public {3} {0} => {1}?.{2};", myName, name, mapName.Name, type);
|
||
else
|
||
WriteLine("public {3} {0} => {1} != null ? {1}.{2} : 0;", myName, name, mapName.Name, mapName.DataType.Name);
|
||
}
|
||
}
|
||
|
||
WriteLine();
|
||
}
|
||
|
||
WriteLine("#endregion");
|
||
}
|
||
|
||
/// <summary>按时间删除数据</summary>
|
||
protected virtual void BuildDelete(IDataColumn column)
|
||
{
|
||
WriteLine("#region 数据清理");
|
||
|
||
WriteLine("/// <summary>清理指定时间段内的数据</summary>");
|
||
WriteLine("/// <param name=\"start\">开始时间。未指定时清理小于指定时间的所有数据</param>");
|
||
WriteLine("/// <param name=\"end\">结束时间</param>");
|
||
WriteLine("/// <returns>清理行数</returns>");
|
||
WriteLine("public static Int32 DeleteWith(DateTime start, DateTime end)");
|
||
WriteLine("{");
|
||
{
|
||
// 分为时间、雪花Id、字符串三种
|
||
var type = column.DataType;
|
||
if (type == typeof(DateTime))
|
||
{
|
||
WriteLine("if (start == end) return Delete(_.{0} == start);", column.Name);
|
||
WriteLine();
|
||
WriteLine("return Delete(_.{0}.Between(start, end));", column.Name);
|
||
}
|
||
else if (type == typeof(Int64))
|
||
{
|
||
//WriteLine("var where = new WhereExpression()");
|
||
//WriteLine("if (start.Year > 2000) where &= _.{0} >= Meta.Factory.Snow.GetId(start)", column.Name);
|
||
//WriteLine("if (end.Year > 2000) where &= _.{0} < Meta.Factory.Snow.GetId(end)", column.Name);
|
||
WriteLine("return Delete(_.{0}.Between(start, end, Meta.Factory.Snow));", column.Name);
|
||
}
|
||
else if (type == typeof(String))
|
||
{
|
||
WriteLine("if (start == end) return Delete(_.{0} == start);", column.Name);
|
||
WriteLine();
|
||
WriteLine("var where = new WhereExpression();");
|
||
WriteLine("if (start.Year > 2000) where &= _.{0} >= start;", column.Name);
|
||
WriteLine("if (end.Year > 2000) where &= _.{0} < end;", column.Name);
|
||
WriteLine("return Delete(where);");
|
||
}
|
||
}
|
||
WriteLine("}");
|
||
|
||
// 对于分库分表的大数据表,生成删表方法
|
||
if (column.DataScale.StartsWithIgnoreCase("timeShard:"))
|
||
{
|
||
WriteLine();
|
||
WriteLine("/// <summary>删除指定时间段内的数据表</summary>");
|
||
WriteLine("/// <param name=\"start\">开始时间</param>");
|
||
WriteLine("/// <param name=\"end\">结束时间</param>");
|
||
WriteLine("/// <returns>清理行数</returns>");
|
||
WriteLine("public static Int32 DropWith(DateTime start, DateTime end)");
|
||
WriteLine("{");
|
||
{
|
||
WriteLine("return Meta.AutoShard(start, end, session =>", column.Name);
|
||
WriteLine("{");
|
||
{
|
||
WriteLine("try");
|
||
WriteLine("{");
|
||
{
|
||
WriteLine("return session.Execute($\"Drop Table {session.FormatedTableName}\");");
|
||
}
|
||
WriteLine("}");
|
||
WriteLine("catch (Exception ex)");
|
||
WriteLine("{");
|
||
{
|
||
WriteLine("XTrace.WriteException(ex);");
|
||
WriteLine("return 0;");
|
||
}
|
||
WriteLine("}");
|
||
}
|
||
WriteLine("}");
|
||
WriteLine(").Sum();");
|
||
}
|
||
WriteLine("}");
|
||
}
|
||
|
||
WriteLine("#endregion");
|
||
}
|
||
#endregion 数据类
|
||
|
||
#region 业务类
|
||
|
||
/// <summary>对象操作</summary>
|
||
protected virtual void BuildAction()
|
||
{
|
||
WriteLine("#region 对象操作");
|
||
|
||
// 静态构造函数
|
||
BuildCctor();
|
||
|
||
// 验证函数
|
||
WriteLine();
|
||
BuildValid();
|
||
|
||
// 初始化数据
|
||
WriteLine();
|
||
BuildInitData();
|
||
|
||
// 重写添删改
|
||
WriteLine();
|
||
BuildOverride();
|
||
|
||
WriteLine("#endregion");
|
||
|
||
if (Table.Properties["NeedHistory"].ToBoolean())
|
||
{
|
||
WriteLine();
|
||
WriteLine("#region 添加历史记录");
|
||
BuildHistory();
|
||
WriteLine("#endregion");
|
||
}
|
||
}
|
||
|
||
/// <summary>生成静态构造函数</summary>
|
||
protected virtual void BuildCctor()
|
||
{
|
||
WriteLine("static {0}()", ClassName);
|
||
WriteLine("{");
|
||
{
|
||
// 只插入日志
|
||
if (Table.InsertOnly)
|
||
{
|
||
WriteLine("Meta.Table.DataTable.InsertOnly = true;");
|
||
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);
|
||
}
|
||
|
||
// 自动分表
|
||
dc = ScaleColumn;
|
||
if (dc != null && !dc.DataScale.IsNullOrEmpty() && dc.DataScale.StartsWithIgnoreCase("timeShard:"))
|
||
BuildShardPolicy(dc);
|
||
|
||
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(new UserModule { AllowEmpty = false });");
|
||
if (ns.Contains("CreateTime") || ns.Contains("UpdateTime"))
|
||
WriteLine("Meta.Modules.Add<TimeModule>();");
|
||
if (ns.Contains("CreateIP") || ns.Contains("UpdateIP"))
|
||
WriteLine("Meta.Modules.Add(new IPModule { AllowEmpty = false });");
|
||
if (ns.Contains("TraceId"))
|
||
WriteLine("Meta.Modules.Add<TraceModule>();");
|
||
if (ns.Contains("TenantId"))
|
||
WriteLine("Meta.Modules.Add<TenantModule>();");
|
||
|
||
if (!Table.InsertOnly && !Table.Name.EndsWith("Log") && !Table.Name.EndsWith("History") && !Table.Name.EndsWith("Record"))
|
||
{
|
||
// 实体缓存
|
||
{
|
||
WriteLine();
|
||
WriteLine("// 实体缓存");
|
||
WriteLine("// var ec = Meta.Cache;");
|
||
WriteLine("// ec.Expire = 60;");
|
||
}
|
||
|
||
// 唯一索引不是主键,又刚好是Master,使用单对象缓存从键
|
||
var di = Table.Indexes.FirstOrDefault(e => e.Unique && e.Columns.Length == 1 && (Table.GetColumn(e.Columns[0])?.Master ?? false));
|
||
if (di != null)
|
||
{
|
||
dc = Table.GetColumn(di.Columns[0]);
|
||
if (dc != null)
|
||
{
|
||
WriteLine();
|
||
WriteLine("// 单对象缓存");
|
||
WriteLine("var sc = Meta.SingleCache;");
|
||
WriteLine("// sc.Expire = 60;");
|
||
WriteLine("sc.FindSlaveKeyMethod = k => Find(_.{0} == k);", dc.Name);
|
||
WriteLine("sc.GetSlaveKeyMethod = e => e.{0};", dc.Name);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
WriteLine("}");
|
||
}
|
||
|
||
/// <summary>生成分表策略</summary>
|
||
/// <param name="dc"></param>
|
||
protected virtual void BuildShardPolicy(IDataColumn dc)
|
||
{
|
||
if (dc.DataScale.IsNullOrEmpty()) return;
|
||
|
||
var ss = dc.DataScale.Split(":");
|
||
var tablePolicy = ss.Length >= 2 ? ss[1] : "";
|
||
var connPolicy = ss.Length >= 3 ? ss[2] : "";
|
||
if (!connPolicy.IsNullOrEmpty() || !tablePolicy.IsNullOrEmpty())
|
||
{
|
||
WriteLine();
|
||
|
||
if (tablePolicy.Contains("HH") || connPolicy.Contains("HH"))
|
||
WriteLine("// 按小时分表");
|
||
else if (tablePolicy.Contains("dd") || connPolicy.Contains("dd"))
|
||
WriteLine("// 按天分表");
|
||
else if (tablePolicy.Contains("MM") || connPolicy.Contains("MM"))
|
||
WriteLine("// 按月分表");
|
||
else if (tablePolicy.Contains("yy") || connPolicy.Contains("yy"))
|
||
WriteLine("// 按年分表");
|
||
else
|
||
WriteLine("// 按[{0}/{1}]分表", connPolicy, tablePolicy);
|
||
|
||
WriteLine("Meta.ShardPolicy = new TimeShardPolicy(nameof({0}), Meta.Factory)", dc.Name);
|
||
WriteLine("{");
|
||
if (!connPolicy.IsNullOrEmpty())
|
||
WriteLine($" ConnPolicy = \"{{0}}_{{1:{connPolicy}}}\",");
|
||
if (!tablePolicy.IsNullOrEmpty())
|
||
WriteLine($" TablePolicy = \"{{0}}_{{1:{tablePolicy}}}\",");
|
||
|
||
if (tablePolicy.Contains("HH") || connPolicy.Contains("HH"))
|
||
WriteLine(" Step = TimeSpan.FromHours(1),");
|
||
else if (tablePolicy.Contains("dd") || connPolicy.Contains("dd"))
|
||
WriteLine(" Step = TimeSpan.FromDays(1),");
|
||
else if (tablePolicy.Contains("MM") || connPolicy.Contains("MM"))
|
||
WriteLine(" Step = TimeSpan.FromDays(30),");
|
||
else if (tablePolicy.Contains("yy") || connPolicy.Contains("yy"))
|
||
WriteLine(" Step = TimeSpan.FromDays(365),");
|
||
else
|
||
WriteLine(" Step = TimeSpan.FromDays(1),");
|
||
|
||
WriteLine("};");
|
||
}
|
||
}
|
||
|
||
static String[] _validExcludes = ["CreateUser", "CreateUserID", "CreateTime", "CreateIP", "UpdateUser", "UpdateUserID", "UpdateTime", "UpdateIP", "Remark", "TraceId"];
|
||
/// <summary>数据验证</summary>
|
||
protected virtual void BuildValid()
|
||
{
|
||
WriteLine("/// <summary>验证并修补数据,返回验证结果,或者通过抛出异常的方式提示验证失败。</summary>");
|
||
WriteLine("/// <param name=\"method\">添删改方法</param>");
|
||
WriteLine("public override Boolean Valid(DataMethod method)");
|
||
WriteLine("{");
|
||
{
|
||
WriteLine("//if (method == DataMethod.Delete) return true;");
|
||
WriteLine("// 如果没有脏数据,则不需要进行任何处理");
|
||
WriteLine("if (!HasDirty) return true;");
|
||
|
||
// 非空判断,字符串且没有默认值
|
||
var cs = Table.Columns.Where(e => !e.Nullable && e.DataType == typeof(String) && e.DefaultValue.IsNullOrEmpty()).ToArray();
|
||
// 剔除CreateUser/UpdateUser等特殊字段
|
||
cs = cs.Where(e => !e.Name.EqualIgnoreCase(_validExcludes)).ToArray();
|
||
if (cs.Length > 0)
|
||
{
|
||
// 有索引的字段判断Empty,不允许空字符串(不利于索引),其它判断null
|
||
var ds = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
|
||
foreach (var di in Table.Indexes)
|
||
{
|
||
foreach (var item in Table.GetColumns(di.Columns))
|
||
{
|
||
if (!ds.Contains(item.Name)) ds.Add(item.Name);
|
||
}
|
||
}
|
||
// 主要字段也判断
|
||
foreach (var item in Table.Columns)
|
||
{
|
||
if (item.Master && !ds.Contains(item.Name)) ds.Add(item.Name);
|
||
}
|
||
|
||
WriteLine();
|
||
WriteLine("// 这里验证参数范围,建议抛出参数异常,指定参数名,前端用户界面可以捕获参数异常并聚焦到对应的参数输入框");
|
||
foreach (var item in cs)
|
||
{
|
||
if (ds.Contains(item.Name))
|
||
WriteLine("if ({0}.IsNullOrEmpty()) throw new ArgumentNullException({1}, \"{2}不能为空!\");", item.Name, NameOf(item.Name), item.DisplayName ?? item.Name);
|
||
else
|
||
WriteLine("if ({0} == null) throw new ArgumentNullException({1}, \"{2}不能为空!\");", item.Name, NameOf(item.Name), item.DisplayName ?? item.Name);
|
||
}
|
||
}
|
||
|
||
WriteLine();
|
||
WriteLine("// 建议先调用基类方法,基类方法会做一些统一处理");
|
||
WriteLine("if (!base.Valid(method)) return false;");
|
||
|
||
WriteLine();
|
||
WriteLine("// 在新插入数据或者修改了指定字段时进行修正");
|
||
|
||
// 保留小数位数
|
||
cs = Table.Columns.Where(e => e.DataType == typeof(Double)).ToArray();
|
||
if (cs.Length > 0)
|
||
{
|
||
WriteLine();
|
||
WriteLine("// 保留2位小数");
|
||
foreach (var item in cs)
|
||
{
|
||
WriteLine("//{0} = Math.Round({0}, 2);", item.Name);
|
||
}
|
||
}
|
||
|
||
// 处理当前已登录用户信息
|
||
cs = Table.Columns.Where(e => e.DataType == typeof(Int32) && e.Name.EqualIgnoreCase("CreateUserID", "UpdateUserID")).ToArray();
|
||
if (cs.Length > 0)
|
||
{
|
||
WriteLine();
|
||
WriteLine("// 处理当前已登录用户信息,可以由UserModule过滤器代劳");
|
||
WriteLine("/*var user = ManageProvider.User;");
|
||
WriteLine("if (user != null)");
|
||
WriteLine("{");
|
||
foreach (var item in cs)
|
||
{
|
||
if (item.Name.EqualIgnoreCase("CreateUserID"))
|
||
WriteLine("if (method == DataMethod.Insert && !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 (method == DataMethod.Insert && !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 (method == DataMethod.Insert && !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(method == DataMethod.Insert, {0});", Table.GetColumns(item.Columns).Select(e => $"nameof({e.Name})").Join(", "));
|
||
}
|
||
}
|
||
WriteLine();
|
||
WriteLine("return true;");
|
||
}
|
||
WriteLine("}");
|
||
}
|
||
|
||
/// <summary>初始化数据</summary>
|
||
protected virtual void BuildInitData()
|
||
{
|
||
var name = ClassName;
|
||
|
||
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 column in Table.Columns)
|
||
{
|
||
if (column.Identity) continue;
|
||
if (column.DataType == null) continue;
|
||
|
||
// 跳过排除项
|
||
if (Option.Excludes.Contains(column.Name, StringComparer.OrdinalIgnoreCase)) continue;
|
||
if (Option.Excludes.Contains(column.ColumnName, StringComparer.OrdinalIgnoreCase)) continue;
|
||
if (_validExcludes.Contains(column.Name, StringComparer.OrdinalIgnoreCase)) continue;
|
||
|
||
switch (column.DataType.GetTypeCode())
|
||
{
|
||
case TypeCode.Boolean:
|
||
WriteLine("// entity.{0} = true;", column.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;", column.Name);
|
||
break;
|
||
|
||
case TypeCode.Single:
|
||
case TypeCode.Double:
|
||
case TypeCode.Decimal:
|
||
WriteLine("// entity.{0} = 0.0;", column.Name);
|
||
break;
|
||
|
||
case TypeCode.DateTime:
|
||
WriteLine("// entity.{0} = DateTime.Now;", column.Name);
|
||
break;
|
||
|
||
case TypeCode.String:
|
||
WriteLine("// entity.{0} = \"abc\";", column.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 BuildHistory()
|
||
{
|
||
//判断生成的表是否包含历史记录表,包含需要添加历史记录信息表
|
||
var needHistory = Table.Properties.FirstOrDefault(x => x.Key.EqualIgnoreCase("NeedHistory"));
|
||
if (Convert.ToBoolean(needHistory.Value))
|
||
{
|
||
WriteLine("/// <summary>重写添加历史记录信息/summary>");
|
||
WriteLine("/// <returns></returns>");
|
||
WriteLine("public override Int32 Insert()");
|
||
WriteLine("{");
|
||
WriteLine($"using var tran = Entity<{Table.Name}>.Meta.CreateTrans(); ");
|
||
WriteLine("var list = new List<IEntity>(); ");
|
||
WriteLine("var ires = base.OnInsert(); ");
|
||
WriteLine("if (ires > 0) AddHistory(this); ");
|
||
WriteLine("tran.Commit(); ");
|
||
WriteLine("return ires; ");
|
||
WriteLine("}");
|
||
WriteLine();
|
||
WriteLine("/// <summary>重写删除历史记录信息</summary>");
|
||
WriteLine("/// <returns></returns>");
|
||
WriteLine("protected override Int32 OnDelete()");
|
||
WriteLine("{");
|
||
WriteLine($"using var tran = Entity<{Table.Name}>.Meta.CreateTrans(); ");
|
||
WriteLine("var list = new List<IEntity>(); ");
|
||
WriteLine("var ires = base.OnDelete(); ");
|
||
WriteLine("if (ires > 0) AddHistory(this); ");
|
||
WriteLine("tran.Commit(); ");
|
||
WriteLine("return ires; ");
|
||
WriteLine("}");
|
||
WriteLine("/// <summary>重写更新历史记录信息</summary>");
|
||
WriteLine("/// <returns></returns>");
|
||
WriteLine("protected override Int32 OnUpdate()");
|
||
WriteLine("{");
|
||
WriteLine($"using var tran = Entity<{Table.Name}>.Meta.CreateTrans(); ");
|
||
WriteLine("var list = new List<IEntity>(); ");
|
||
WriteLine("var ires = base.OnUpdate(); ");
|
||
WriteLine("if (ires > 0) AddHistory(this); ");
|
||
WriteLine("tran.Commit(); ");
|
||
WriteLine("return ires; ");
|
||
WriteLine("}");
|
||
|
||
WriteLine("/// <summary>添加历史记录信息</summary>");
|
||
WriteLine("/// <returns></returns>");
|
||
WriteLine($"private int AddHistory({Table.Name} entity) ");
|
||
WriteLine("{");
|
||
{
|
||
var History = Table.Name + "History" + " entityHistory = new " + Table.Name + "History();";
|
||
WriteLine(History);
|
||
var tablePrimaryKey = Table.PrimaryKeys?.Where(o => o.Name.ToUpper().Contains("ID"));
|
||
var primaryKey = tablePrimaryKey.Count() > 0 ? tablePrimaryKey.FirstOrDefault()?.Name : "ID";
|
||
WriteLine("XCode.Common.DataConversion.CopyProperty(entity, entityHistory);");
|
||
WriteLine($"entityHistory.{primaryKey} = 0; ");
|
||
WriteLine($"entityHistory.{Table.Name}ID = entity.{primaryKey}; ");
|
||
WriteLine("return entityHistory.Insert(); ");
|
||
}
|
||
WriteLine("}");
|
||
}
|
||
|
||
}
|
||
|
||
/// <summary>扩展属性</summary>
|
||
protected virtual void BuildExtendProperty()
|
||
{
|
||
WriteLine("#region 扩展属性");
|
||
|
||
var first = true;
|
||
foreach (var column in Table.Columns)
|
||
{
|
||
// 跳过排除项
|
||
if (Option.Excludes.Contains(column.Name)) continue;
|
||
if (Option.Excludes.Contains(column.ColumnName)) continue;
|
||
if (!column.Map.IsNullOrEmpty()) continue;
|
||
|
||
// 找到名字映射
|
||
var dt = AllTables.FirstOrDefault(
|
||
e => e.PrimaryKeys.Length == 1 &&
|
||
e.PrimaryKeys[0].DataType == column.DataType &&
|
||
(e.Name + e.PrimaryKeys[0].Name).EqualIgnoreCase(column.Name));
|
||
|
||
if (dt != null)
|
||
{
|
||
// 属性名
|
||
var pname = dt.Name;
|
||
|
||
// 备注
|
||
var dis = column.DisplayName;
|
||
if (dis.IsNullOrEmpty()) dis = dt.DisplayName;
|
||
|
||
var pk = dt.PrimaryKeys[0];
|
||
|
||
if (!first)
|
||
{
|
||
WriteLine();
|
||
first = true;
|
||
}
|
||
|
||
WriteLine("/// <summary>{0}</summary>", dis);
|
||
WriteLine("[XmlIgnore, IgnoreDataMember, ScriptIgnore]");
|
||
if (Option.Nullable)
|
||
WriteLine("public {1}? {1} => Extends.Get({0}, k => {1}.FindBy{3}({2}));", NameOf(pname), dt.Name, column.Name, pk.Name);
|
||
else
|
||
WriteLine("public {1} {1} => Extends.Get({0}, k => {1}.FindBy{3}({2}));", NameOf(pname), dt.Name, column.Name, pk.Name);
|
||
|
||
// 主字段
|
||
var master = dt.Master ?? dt.GetColumn("Name");
|
||
// 扩展属性有可能恰巧跟已有字段同名
|
||
if (master != null && !master.PrimaryKey && !Table.Columns.Any(e => e.Name.EqualIgnoreCase(pname + master.Name)))
|
||
{
|
||
WriteLine();
|
||
WriteLine("/// <summary>{0}</summary>", dis);
|
||
WriteLine("[Map(nameof({0}), typeof({1}), \"{2}\")]", column.Name, dt.Name, pk.Name);
|
||
if (column.Properties.TryGetValue("Category", out var att) && !att.IsNullOrEmpty())
|
||
WriteLine("[Category(\"{0}\")]", att);
|
||
if (master.DataType == typeof(String))
|
||
{
|
||
if (Option.Nullable)
|
||
WriteLine("public {2}? {0}{1} => {0}?.{1};", pname, master.Name, master.DataType.Name);
|
||
else
|
||
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();
|
||
}
|
||
}
|
||
|
||
WriteLine("#endregion");
|
||
}
|
||
|
||
/// <summary>扩展查询</summary>
|
||
protected virtual void BuildExtendSearch()
|
||
{
|
||
WriteLine("#region 扩展查询");
|
||
|
||
var methods = 0;
|
||
|
||
// 主键
|
||
var pks = Table.PrimaryKeys;
|
||
if (pks.Length > 0)
|
||
{
|
||
if (BuildExtendFind(pks, methods)) methods++;
|
||
}
|
||
|
||
// 索引
|
||
var pk = pks.FirstOrDefault();
|
||
foreach (var di in Table.Indexes)
|
||
{
|
||
// 跳过主键
|
||
if (di.Columns.Length == 1 && pk != null && di.Columns[0].EqualIgnoreCase(pk.Name, pk.ColumnName)) continue;
|
||
|
||
// 超过3字段索引,不要生成查询函数
|
||
if (di.Columns.Length > 3) continue;
|
||
|
||
var cs = Table.GetColumns(di.Columns);
|
||
if (cs == null || cs.Length != di.Columns.Length) continue;
|
||
|
||
// 索引最后一个字段如果是主键Id,则该不参与生成查询方法
|
||
if (pk != null && cs[^1].ColumnName.EqualIgnoreCase(pk.ColumnName))
|
||
{
|
||
cs = cs.Take(cs.Length - 1).ToArray();
|
||
}
|
||
|
||
// 只有整数和字符串能生成查询函数
|
||
if (cs.Any(e => !CanSearch(e))) continue;
|
||
|
||
// 唯一索引
|
||
if (di.Unique)
|
||
{
|
||
if (BuildExtendFind(cs, methods)) methods++;
|
||
|
||
// 生成多个前缀查询
|
||
for (var i = 1; i < cs.Length; i++)
|
||
{
|
||
// 如果当前字段不可查询,则终止
|
||
if (!CanSearch(cs[i - 1])) break;
|
||
|
||
var cs2 = cs.Take(i).ToArray();
|
||
|
||
if (BuildExtendFindAll(cs2, methods)) methods++;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (BuildExtendFindAll(cs, methods)) methods++;
|
||
}
|
||
}
|
||
|
||
// 作为多个索引头部的字段,生成FindAll。例如多个索引都使用Ds开头,那么应该生成FindAllByDs
|
||
var mcolumns = new List<String>();
|
||
foreach (var di in Table.Indexes)
|
||
{
|
||
if (di.Columns.Length > 1) mcolumns.Add(di.Columns[0]);
|
||
}
|
||
// 实际生成时,可能已经有了FindAllByDs,则根据方法名和签名跳过
|
||
foreach (var item in mcolumns.GroupBy(e => e))
|
||
{
|
||
if (item.Count() > 1)
|
||
{
|
||
var dc = Table.GetColumn(item.Key);
|
||
if (dc != null)
|
||
{
|
||
if (BuildExtendFindAll([dc], methods)) methods++;
|
||
}
|
||
}
|
||
}
|
||
|
||
WriteLine("#endregion");
|
||
}
|
||
|
||
/// <summary>生成扩展Find查询单对象</summary>
|
||
/// <param name="columns"></param>
|
||
/// <param name="index"></param>
|
||
protected virtual Boolean BuildExtendFind(IDataColumn[] columns, Int32 index)
|
||
{
|
||
var methodName = columns.Select(e => e.Name).Join("And");
|
||
methodName = $"FindBy{methodName}";
|
||
|
||
var (ps, ps2) = GetParameters(columns);
|
||
var args = ps.Join(", ", e => $"{e.Value} {e.Key}");
|
||
|
||
// 如果方法名已存在,则不生成
|
||
var key = $"{methodName}({ps2.Join(",", e => e.Value)})";
|
||
if (Members.Contains(key)) return false;
|
||
Members.Add(key);
|
||
|
||
if (index > 0) WriteLine();
|
||
WriteLine("/// <summary>根据{0}查找</summary>", columns.Select(e => e.DisplayName).Join("、"));
|
||
foreach (var dc in columns)
|
||
{
|
||
WriteLine("/// <param name=\"{0}\">{1}</param>", dc.CamelName(), dc.DisplayName);
|
||
}
|
||
|
||
var nullable = EntityOption.Nullable;
|
||
|
||
WriteLine("/// <returns>实体对象</returns>");
|
||
WriteLine("public static {2} {0}({1})", methodName, args, ClassName + (nullable ? "?" : ""));
|
||
WriteLine("{");
|
||
{
|
||
var header = false;
|
||
foreach (var dc in columns)
|
||
{
|
||
if (dc.DataType != null && dc.DataType.IsInt())
|
||
WriteLine("if ({0} < 0) return null;", dc.CamelName());
|
||
else if (dc.DataType == typeof(String))
|
||
{
|
||
if (nullable)
|
||
WriteLine("if ({0} == null) return null;", dc.CamelName());
|
||
else
|
||
WriteLine("if ({0}.IsNullOrEmpty()) return null;", dc.CamelName());
|
||
}
|
||
else if (dc.DataType == typeof(DateTime) && dc.ItemType.EqualIgnoreCase("date"))
|
||
WriteLine("if ({0}.Year < 2000) return null;", dc.CamelName());
|
||
|
||
header |= CanSearch(dc);
|
||
}
|
||
|
||
var exp = new StringBuilder();
|
||
var wh = new StringBuilder();
|
||
foreach (var dc in columns)
|
||
{
|
||
if (exp.Length > 0) exp.Append(" & ");
|
||
exp.AppendFormat("_.{0} == {1}", dc.Name, dc.CamelName());
|
||
|
||
if (wh.Length > 0) wh.Append(" && ");
|
||
if (dc.DataType == typeof(String))
|
||
wh.AppendFormat("e.{0}.EqualIgnoreCase({1})", dc.Name, dc.CamelName());
|
||
else
|
||
wh.AppendFormat("e.{0} == {1}", dc.Name, dc.CamelName());
|
||
}
|
||
|
||
var singleCache = false;
|
||
if (UsingCache)
|
||
{
|
||
if (header) WriteLine();
|
||
WriteLine("// 实体缓存");
|
||
WriteLine("if (Meta.Session.Count < 1000) return Meta.Cache.Find(e => {0});", wh);
|
||
|
||
// 单对象缓存
|
||
if (columns.Length == 1)
|
||
{
|
||
var pk = columns[0];
|
||
if (pk.PrimaryKey)
|
||
{
|
||
WriteLine();
|
||
WriteLine("// 单对象缓存");
|
||
WriteLine("return Meta.SingleCache[{0}];", pk.CamelName());
|
||
|
||
singleCache = true;
|
||
}
|
||
else if (pk.Master)
|
||
{
|
||
WriteLine();
|
||
WriteLine("// 单对象缓存");
|
||
WriteLine("return Meta.SingleCache.GetItemWithSlaveKey({0}) as {1};", pk.CamelName(), ClassName);
|
||
|
||
singleCache = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (header) WriteLine();
|
||
|
||
if (singleCache)
|
||
WriteLine("//return Find({0});", exp);
|
||
else
|
||
WriteLine("return Find({0});", exp);
|
||
}
|
||
WriteLine("}");
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <summary>生成扩展FindAll查询对象列表</summary>
|
||
/// <param name="columns"></param>
|
||
/// <param name="index"></param>
|
||
protected virtual Boolean BuildExtendFindAll(IDataColumn[] columns, Int32 index)
|
||
{
|
||
var methodName = columns.Select(e => e.Name).Join("And");
|
||
methodName = $"FindAllBy{methodName}";
|
||
|
||
var (ps, ps2) = GetParameters(columns);
|
||
var args = ps.Join(", ", e => $"{e.Value} {e.Key}");
|
||
|
||
// 如果方法名已存在,则不生成
|
||
var key = $"{methodName}({ps2.Join(",", e => e.Value)})";
|
||
if (Members.Contains(key)) return false;
|
||
Members.Add(key);
|
||
|
||
if (index > 0) WriteLine();
|
||
WriteLine("/// <summary>根据{0}查找</summary>", columns.Select(e => e.DisplayName).Join("、"));
|
||
foreach (var dc in columns)
|
||
{
|
||
WriteLine("/// <param name=\"{0}\">{1}</param>", dc.CamelName(), dc.DisplayName);
|
||
}
|
||
|
||
WriteLine("/// <returns>实体列表</returns>");
|
||
WriteLine("public static IList<{2}> {0}({1})", methodName, args, ClassName);
|
||
WriteLine("{");
|
||
{
|
||
var header = false;
|
||
foreach (var dc in columns)
|
||
{
|
||
if (dc.DataType != null && dc.DataType.IsInt())
|
||
WriteLine("if ({0} < 0) return [];", dc.CamelName(), ClassName);
|
||
else if (dc.DataType == typeof(String))
|
||
{
|
||
if (Option.Nullable)
|
||
WriteLine("if ({0} == null) return [];", dc.CamelName());
|
||
else
|
||
WriteLine("if ({0}.IsNullOrEmpty()) return [];", dc.CamelName(), ClassName);
|
||
}
|
||
else if (dc.DataType == typeof(DateTime) && dc.ItemType.EqualIgnoreCase("date"))
|
||
WriteLine("if ({0}.Year < 2000) return [];", dc.CamelName());
|
||
|
||
header |= CanSearch(dc);
|
||
}
|
||
|
||
var exp = new StringBuilder();
|
||
var wh = new StringBuilder();
|
||
foreach (var dc in columns)
|
||
{
|
||
if (exp.Length > 0) exp.Append(" & ");
|
||
exp.AppendFormat("_.{0} == {1}", dc.Name, dc.CamelName());
|
||
|
||
if (wh.Length > 0) wh.Append(" && ");
|
||
if (dc.DataType == typeof(String))
|
||
wh.AppendFormat("e.{0}.EqualIgnoreCase({1})", dc.Name, dc.CamelName());
|
||
else
|
||
wh.AppendFormat("e.{0} == {1}", dc.Name, dc.CamelName());
|
||
}
|
||
|
||
if (UsingCache)
|
||
{
|
||
if (header) WriteLine();
|
||
WriteLine("// 实体缓存");
|
||
WriteLine("if (Meta.Session.Count < 1000) return Meta.Cache.FindAll(e => {0});", wh);
|
||
}
|
||
|
||
if (header) WriteLine();
|
||
WriteLine("return FindAll({0});", exp);
|
||
}
|
||
WriteLine("}");
|
||
|
||
return true;
|
||
}
|
||
|
||
private (IDictionary<String, String>, IDictionary<String, String>) GetParameters(IList<IDataColumn> columns)
|
||
{
|
||
var ps = new Dictionary<String, String>();
|
||
var ps2 = new Dictionary<String, String>();
|
||
foreach (var dc in columns)
|
||
{
|
||
var type = dc.Properties["Type"];
|
||
if (type.IsNullOrEmpty()) type = dc.DataType?.Name + "";
|
||
|
||
if (dc.DataType == typeof(Boolean))
|
||
type += "?";
|
||
|
||
ps[dc.CamelName()] = type;
|
||
|
||
var p = type.LastIndexOf('.');
|
||
if (p > 0) type = type[(p + 1)..];
|
||
ps2[dc.CamelName()] = type;
|
||
}
|
||
|
||
return (ps, ps2);
|
||
}
|
||
|
||
/// <summary>自定义查询区域</summary>
|
||
protected virtual void BuildSearch()
|
||
{
|
||
WriteLine("#region 高级查询");
|
||
|
||
var cs = BuildAdvanceSearch();
|
||
|
||
var idx = Table.Indexes ?? [];
|
||
var returnName = ClassName;
|
||
|
||
// 字段缓存,用于魔方前台下拉选择
|
||
if (idx != null && idx.Count > 0)
|
||
{
|
||
// 主键和时间字段
|
||
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;
|
||
var names = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
|
||
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;
|
||
if (names.Contains(name)) continue;
|
||
names.Add(name);
|
||
|
||
WriteLine();
|
||
WriteLine($"// Select Count({pname}) as {pname},{name} From {Table.TableName} 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.TableName} 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 IList<IDataColumn> BuildAdvanceSearch()
|
||
{
|
||
// 收集索引信息,索引中的所有字段都参与,构造一个高级查询模板
|
||
var idx = Table.Indexes ?? [];
|
||
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);
|
||
}
|
||
}
|
||
|
||
if (cs.Count <= 0) return cs;
|
||
|
||
var returnName = ClassName;
|
||
|
||
// 时间字段。无差别支持UpdateTime/CreateTime
|
||
var dcTime = cs.FirstOrDefault(e => e.DataScale.StartsWithIgnoreCase("time"));
|
||
dcTime ??= cs.FirstOrDefault(e => e.DataType == typeof(DateTime));
|
||
dcTime ??= Table.GetColumns(["UpdateTime", "CreateTime"])?.FirstOrDefault();
|
||
var dcSnow = cs.FirstOrDefault(e => e.PrimaryKey && !e.Identity && e.DataType == typeof(Int64));
|
||
|
||
if (dcTime != null) cs.Remove(dcTime);
|
||
cs.RemoveAll(e => e.Name.EqualIgnoreCase("key", "page"));
|
||
if (dcSnow != null || dcTime != null)
|
||
cs.RemoveAll(e => e.Name.EqualIgnoreCase("start", "end"));
|
||
|
||
var (ps, ps2) = GetParameters(cs);
|
||
|
||
// 如果方法名已存在,则不生成
|
||
if (dcTime != null)
|
||
{
|
||
ps2["start"] = "DateTime";
|
||
ps2["end"] = "DateTime";
|
||
}
|
||
ps2["key"] = "String";
|
||
ps2["page"] = "PageParameter";
|
||
var key = $"Search({ps2.Join(",", e => e.Value)})";
|
||
if (Members.Contains(key)) return cs;
|
||
Members.Add(key);
|
||
|
||
// 可用于关键字模糊搜索的字段
|
||
var keys = Table.Columns.Where(e => e.DataType == typeof(String)).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);
|
||
}
|
||
else if (dcSnow != null)
|
||
{
|
||
WriteLine("/// <param name=\"start\">{0}开始</param>", dcSnow.DisplayName);
|
||
WriteLine("/// <param name=\"end\">{0}结束</param>", dcSnow.DisplayName);
|
||
}
|
||
WriteLine("/// <param name=\"key\">关键字</param>");
|
||
WriteLine("/// <param name=\"page\">分页参数信息。可携带统计和数据权限扩展查询等信息</param>");
|
||
WriteLine("/// <returns>实体列表</returns>");
|
||
|
||
// 参数部分
|
||
var pis = ps.Join(", ", e => $"{e.Value} {e.Key}");
|
||
var piTime = dcTime == null ? "" : "DateTime start, DateTime end, ";
|
||
if (pis.Length > 0)
|
||
WriteLine("public static IList<{0}> Search({1}, {2}String key, PageParameter page)", returnName, pis, piTime);
|
||
else
|
||
WriteLine("public static IList<{0}> Search({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 (dcSnow != null)
|
||
WriteLine("exp &= _.{0}.Between(start, end, Meta.Factory.Snow);", dcSnow.Name);
|
||
else 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("}");
|
||
|
||
return cs;
|
||
}
|
||
|
||
/// <summary>业务操作</summary>
|
||
protected virtual void BuildBusiness()
|
||
{
|
||
WriteLine("#region 业务操作");
|
||
var toModel = EntityOption.ModelNameForToModel;
|
||
var model = Option.ModelNameForCopy;
|
||
if (!toModel.IsNullOrEmpty() && !model.IsNullOrEmpty())
|
||
{
|
||
BuildToModel(toModel.Replace("{name}", ClassName), model.Replace("{name}", ClassName));
|
||
WriteLine("");
|
||
}
|
||
WriteLine("#endregion");
|
||
}
|
||
|
||
#endregion 业务类
|
||
|
||
#region 辅助
|
||
/// <summary>该列是否能够搜索。整型、字符串以及日期类型可以搜索</summary>
|
||
/// <param name="column"></param>
|
||
/// <returns></returns>
|
||
private Boolean CanSearch(IDataColumn column)
|
||
{
|
||
if (column.DataType == null) return false;
|
||
|
||
if (column.DataType.IsInt()) return true;
|
||
if (column.DataType == typeof(String)) return true;
|
||
if (column.DataType == typeof(DateTime) && column.ItemType.EqualIgnoreCase("date")) return true;
|
||
|
||
return false;
|
||
}
|
||
#endregion
|
||
} |