From b814921a6ac35973861f8d46c86db062fdd7fb1d Mon Sep 17 00:00:00 2001
From: 2881099 <2881099@qq.com>
Date: Wed, 13 Dec 2023 20:59:25 +0800
Subject: [PATCH] =?UTF-8?q?-=20=E5=A2=9E=E5=8A=A0=20=E4=BD=8E=E4=BB=A3?=
=?UTF-8?q?=E7=A0=81=E6=89=A9=E5=B1=95=E5=8C=85=20FreeSql.Extensions.ZoreE?=
=?UTF-8?q?ntity=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../FreeSql.Extensions.ZoreEntity.csproj | 43 +
.../FreeSql.Extensions.ZoreEntity.xml | 67 +
.../ZoreDbContext.SelectImpl.cs | 915 ++++++++++
.../ZoreDbContext.cs | 1476 +++++++++++++++++
.../ZoreDescriptor.cs | 97 ++
.../FreeSql.Extensions.ZoreEntity/key.snk | Bin 0 -> 596 bytes
FreeSql-lite.sln | 19 +-
FreeSql/FreeSql.xml | 210 +--
8 files changed, 2727 insertions(+), 100 deletions(-)
create mode 100644 Extensions/FreeSql.Extensions.ZoreEntity/FreeSql.Extensions.ZoreEntity.csproj
create mode 100644 Extensions/FreeSql.Extensions.ZoreEntity/FreeSql.Extensions.ZoreEntity.xml
create mode 100644 Extensions/FreeSql.Extensions.ZoreEntity/ZoreDbContext.SelectImpl.cs
create mode 100644 Extensions/FreeSql.Extensions.ZoreEntity/ZoreDbContext.cs
create mode 100644 Extensions/FreeSql.Extensions.ZoreEntity/ZoreDescriptor.cs
create mode 100644 Extensions/FreeSql.Extensions.ZoreEntity/key.snk
diff --git a/Extensions/FreeSql.Extensions.ZoreEntity/FreeSql.Extensions.ZoreEntity.csproj b/Extensions/FreeSql.Extensions.ZoreEntity/FreeSql.Extensions.ZoreEntity.csproj
new file mode 100644
index 00000000..807c517a
--- /dev/null
+++ b/Extensions/FreeSql.Extensions.ZoreEntity/FreeSql.Extensions.ZoreEntity.csproj
@@ -0,0 +1,43 @@
+
+
+
+ netstandard2.0;net45;net40
+ true
+ FreeSql;ncc;YeXiangQin
+ FreeSql 扩展包,实现 低代码、零实体类型,并且支持导航属性,级联保存 等功能.
+ https://github.com/2881099/FreeSql
+ https://github.com/2881099/FreeSql
+ git
+ MIT
+ FreeSql;ORM;低代码;
+ $(AssemblyName)
+ logo.png
+ $(AssemblyName)
+ true
+ true
+ true
+ key.snk
+ false
+ 3.2.806-preview20231214
+ readme.md
+
+
+
+
+
+
+
+
+ FreeSql.Extensions.ZoreEntity.xml
+ 3
+
+
+
+ net40
+
+
+
+
+
+
+
diff --git a/Extensions/FreeSql.Extensions.ZoreEntity/FreeSql.Extensions.ZoreEntity.xml b/Extensions/FreeSql.Extensions.ZoreEntity/FreeSql.Extensions.ZoreEntity.xml
new file mode 100644
index 00000000..667a67a9
--- /dev/null
+++ b/Extensions/FreeSql.Extensions.ZoreEntity/FreeSql.Extensions.ZoreEntity.xml
@@ -0,0 +1,67 @@
+
+
+
+ FreeSql.Extensions.ZoreEntity
+
+
+
+
+ 【有状态管理】自动 Include 查询
+
+
+
+
+ 【无状态管理】指定表查询
+
+
+
+
+ Type = Update 的时候,获取更新之前的对象
+
+
+
+
+ 实体变化记录
+
+
+
+
+ 实体变化事件
+
+
+
+
+ 举例1:LeftJoin("table1", "id", "user.id") -> LEFT JOIN [table1] b ON b.[id] = a.[id]
+ 举例2:LeftJoin("table1", "id", "user.id", "xid", "user.xid") -> LEFT JOIN [table1] b ON b.[id] = [a].id] AND b.[xid] = a.[xid]
+
+
+
+
+ 举例1:InnerJoin("table1", "id", "user.id") -> INNER JOIN [table1] b ON b.[id] = a.[id]
+ 举例2:InnerJoin("table1", "id", "user.id", "xid", "user.xid") -> INNER JOIN [table1] b ON b.[id] = [a].id] AND b.[xid] = a.[xid]
+
+
+
+
+ 举例1:RightJoin("table1", "id", "user.id") -> RIGTH JOIN [table1] b ON b.[id] = a.[id]
+ 举例2:RightJoin("table1", "id", "user.id", "xid", "user.xid") -> RIGTH JOIN [table1] b ON b.[id] = [a].id] AND b.[xid] = a.[xid]
+
+
+
+
+ WHERE [Id] IN (...)
+
+
+
+
+ Where(new { Year = 2017, CategoryId = 198, IsPublished = true })
+ WHERE [Year] = 2017 AND [CategoryId] = 198 AND [IsPublished] = 1
+
+
+
+
+ WHERE [field] = ..
+
+
+
+
diff --git a/Extensions/FreeSql.Extensions.ZoreEntity/ZoreDbContext.SelectImpl.cs b/Extensions/FreeSql.Extensions.ZoreEntity/ZoreDbContext.SelectImpl.cs
new file mode 100644
index 00000000..574f7d36
--- /dev/null
+++ b/Extensions/FreeSql.Extensions.ZoreEntity/ZoreDbContext.SelectImpl.cs
@@ -0,0 +1,915 @@
+using FreeSql.DataAnnotations;
+using FreeSql.Internal;
+using FreeSql.Internal.CommonProvider;
+using FreeSql.Internal.Model;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Data.Common;
+using System.Linq;
+using System.Text;
+using T = System.Collections.Generic.Dictionary;
+
+namespace FreeSql.Extensions.ZoreEntity
+{
+ partial class ZoreDbContext
+ {
+ public class SelectImpl
+ {
+ ZoreDbContext _dbcontext;
+ IFreeSql _orm => _dbcontext._orm;
+ List _tables => _dbcontext._tables;
+ int _mainTableIndex = -1;
+ List _tableAlias;
+ ISelect _select;
+ Select0Provider _selectProvider;
+ string _field;
+ Dictionary _fieldAlias;
+ List> _fieldReader;
+ string _groupBy;
+ List _params = new List();
+ CommonUtils _common => _selectProvider._commonUtils;
+ bool _useStates = true;
+ bool _includeAll = false;
+
+ SelectImpl() { }
+ internal SelectImpl(ZoreDbContext dbcontext, string tableName)
+ {
+ _dbcontext = dbcontext;
+ var tableIndex = _tables.FindIndex(a => a.CsName.ToLower() == tableName?.ToLower());
+ if (tableIndex == -1) throw new Exception($"未定义表名 {tableName}");
+
+ _mainTableIndex = tableIndex;
+ _tableAlias = new List();
+ _select = _orm.Select()
+ .AsTable((t, old) => _tables[tableIndex].DbName);
+ _selectProvider = _select as Select0Provider;
+ _fieldAlias = new Dictionary();
+ _fieldReader = new List>();
+ FlagFetchResult(_tables[_mainTableIndex], "a", "");
+ }
+
+ public SelectImpl NoTracking()
+ {
+ _useStates = false;
+ return this;
+ }
+ public SelectImpl IncludeAll()
+ {
+ var ignores = new Dictionary();
+ _includeAll = true;
+ LocalAutoInclude(_tables[_mainTableIndex], "a");
+ return this;
+
+ void LocalAutoInclude(ZoreTableInfo table, string alias, string navPath = "")
+ {
+ if (ignores.ContainsKey(table.CsName)) return;
+ ignores.Add(table.CsName, true);
+ TableAliasInfo tableAlias = null;
+ if (table != _tables[_mainTableIndex])
+ tableAlias = FlagFetchResult(table, alias.ToString(), navPath);
+
+ var leftJoins = table.Navigates.Where(a => a.Value.RefType == TableRefType.ManyToOne || a.Value.RefType == TableRefType.OneToOne).ToArray();
+ foreach (var join in leftJoins)
+ {
+ var joinTable = join.Value.RefTable;
+ if (ignores.ContainsKey(joinTable.CsName)) return;
+
+ var joinAlias = GetMaxAlias();
+ var joinOn = string.Join(" AND ", join.Value.RefColumns.Select((bname, idx) =>
+ $"{joinAlias}.{_common.QuoteSqlName(join.Value.RefTable.ColumnsByCs[bname].Attribute.Name)} = {alias}.{_common.QuoteSqlName(join.Value.Table.ColumnsByCs[join.Value.Columns[idx]].Attribute.Name)}"));
+ _select.LeftJoin($"{_common.QuoteSqlName(join.Value.RefTable.DbName)} {joinAlias} ON {joinOn}");
+
+ LocalAutoInclude(joinTable, joinAlias, $"{navPath}.{join.Key}");
+ }
+ if (tableAlias == null) tableAlias = _tableAlias.Where(a => a.Alias == alias).First();
+ var includeManys = table.Navigates.Where(a => a.Value.RefType == TableRefType.OneToMany || a.Value.RefType == TableRefType.ManyToMany).ToArray();
+ foreach (var includeMany in includeManys)
+ tableAlias.IncludeMany.Add(NativeTuple.Create(includeMany.Key, (Action)null));
+ }
+ }
+ public SelectImpl Include(string navigate, Action then = null)
+ {
+ var alias = _tableAlias[0];
+ var navPath = navigate.Split('.');
+ var navPath2 = "";
+ for (var navIdx = 0; navIdx < navPath.Length; navIdx++)
+ {
+ if (alias.Table.Navigates.TryGetValue(navPath[navIdx], out var nav) == false) throw new Exception($"{alias.Table.CsName} 未定义导航属性 {navPath[navIdx]}");
+ if (nav.RefType == TableRefType.OneToMany || nav.RefType == TableRefType.ManyToMany)
+ {
+ if (navIdx < navPath.Length - 1) throw new Exception($"导航属性 OneToMany/ManyToMany {navPath[navIdx]} 不能处于中间位置");
+ if (alias.IncludeMany.Where(a => a.Item1 == nav.NavigateKey).Any() == false)
+ alias.IncludeMany.Add(NativeTuple.Create(nav.NavigateKey, then));
+ break;
+ }
+ navPath2 = navIdx == 0 ? nav.NavigateKey : $"{navPath2}.{nav.NavigateKey}";
+ var navAlias = _tableAlias.Where(a => string.Join(".", a.NavPath) == navPath2).FirstOrDefault();
+ if (navAlias == null)
+ {
+ var joinAlias = GetMaxAlias();
+ var joinOn = string.Join(" AND ", nav.RefColumns.Select((bname, idx) =>
+ $"{joinAlias}.{_common.QuoteSqlName(nav.RefTable.ColumnsByCs[bname].Attribute.Name)} = {alias.Alias}.{_common.QuoteSqlName(nav.Table.ColumnsByCs[nav.Columns[idx]].Attribute.Name)}"));
+ _select.LeftJoin($"{_common.QuoteSqlName(nav.RefTable.DbName)} {joinAlias} ON {joinOn}");
+ navAlias = FlagFetchResult(nav.RefTable, joinAlias, navPath2);
+ }
+ alias = navAlias;
+ }
+ return this;
+ }
+ //public SelectImpl IncludeSubQuery(string resultKey, string tableName, Action then)
+ //{
+ // var query = _dbcontext.SelectNoTracking(tableName);
+ // query._tableAlias.AddRange(_tableAlias);
+ // return this;
+ //}
+
+ ///
+ /// 举例1:LeftJoin("table1", "id", "user.id") -> LEFT JOIN [table1] b ON b.[id] = a.[id]
+ /// 举例2:LeftJoin("table1", "id", "user.id", "xid", "user.xid") -> LEFT JOIN [table1] b ON b.[id] = [a].id] AND b.[xid] = a.[xid]
+ ///
+ public SelectImpl LeftJoin(string tableName, params string[] onFields) => Join("LEFT JOIN", tableName, onFields);
+ ///
+ /// 举例1:InnerJoin("table1", "id", "user.id") -> INNER JOIN [table1] b ON b.[id] = a.[id]
+ /// 举例2:InnerJoin("table1", "id", "user.id", "xid", "user.xid") -> INNER JOIN [table1] b ON b.[id] = [a].id] AND b.[xid] = a.[xid]
+ ///
+ public SelectImpl InnerJoin(string tableName, params string[] onFields) => Join("INNER JOIN", tableName, onFields);
+ ///
+ /// 举例1:RightJoin("table1", "id", "user.id") -> RIGTH JOIN [table1] b ON b.[id] = a.[id]
+ /// 举例2:RightJoin("table1", "id", "user.id", "xid", "user.xid") -> RIGTH JOIN [table1] b ON b.[id] = [a].id] AND b.[xid] = a.[xid]
+ ///
+ public SelectImpl RightJoin(string tableName, params string[] onFields) => Join("RIGTH JOIN", tableName, onFields);
+ SelectImpl Join(string joinType, string tableName, params string[] onFields)
+ {
+ if (onFields == null || onFields.Length == 0) throw new Exception($"{joinType} 参数 {nameof(onFields)} 不能为空");
+ if (onFields.Length % 2 != 0) throw new Exception($"{joinType} 参数 {nameof(onFields)} 数组长度必须为偶数,正确:.LeftJoin(\"table1\", \"id\", \"user.id\")");
+
+ var table = _tables.Where(a => a.CsName.ToLower() == tableName?.ToLower()).FirstOrDefault();
+ if (table == null) throw new Exception($"未定义表名 {tableName}");
+ var alias = GetMaxAlias();
+ var navKey = tableName;
+ for (var a = 2; true; a++)
+ {
+ if (_tables[_mainTableIndex].ColumnsByCs.ContainsKey(navKey))
+ {
+ navKey = $"{tableName}{a}";
+ continue;
+ }
+ if (_tableAlias.Any(b => b.NavPath.Length == 1 && b.NavPath.Last() == navKey))
+ {
+ navKey = $"{tableName}{a}";
+ continue;
+ }
+ break;
+ }
+ FlagFetchResult(table, alias, navKey);
+ var joinOn = new string[onFields.Length / 2];
+ for (var a = 0; a < onFields.Length; a += 2)
+ {
+ var field1 = ParseField(table, alias, onFields[a]);
+ if (field1 == null) throw new Exception($"未匹配字段名 {onFields[a]}");
+ var field2 = ParseField(table, alias, onFields[a + 1]);
+ if (field2 == null) throw new Exception($"未匹配字段名 {onFields[a + 1]}");
+ joinOn[a / 2] = $"{field1.Item1} = {field2.Item1}";
+ }
+ _select.RawJoin($"{joinType} {_common.QuoteSqlName(table.DbName)} {alias} ON {string.Join(" AND ", joinOn)}");
+ return this;
+ }
+
+ class TableAliasInfo
+ {
+ public string Alias { get; set; }
+ public ZoreTableInfo Table { get; set; }
+ public string[] NavPath { get; set; }
+ public List>> IncludeMany { get; set; } = new List>>();
+ }
+ class DbDataReaderContext
+ {
+ public DbDataReader Reader { get; set; }
+ public int Index { get; set; }
+ public T Result { get; set; }
+ }
+ string GetMaxAlias()
+ {
+ var max = (int)_tableAlias.Where(a => a.Alias.Length == 1).Max(a => a.Alias[0]);
+ if (max < 'a') max = 'a';
+ for (var a = 1; true; a++)
+ {
+ var alias = ((char)(max + a)).ToString();
+ if (_tableAlias.Where(b => b.Alias == alias).Any()) continue;
+ return alias;
+ }
+ }
+ TableAliasInfo FlagFetchResult(ZoreTableInfo table, string alias, string navPath)
+ {
+ var tableAlias = _tableAlias.Where(a => a.Alias == alias).FirstOrDefault();
+ if (tableAlias == null)
+ {
+ var navPathArray = navPath.Split('.').Where(a => string.IsNullOrWhiteSpace(a) == false).ToArray();
+ _tableAlias.Add(tableAlias = new TableAliasInfo
+ {
+ Alias = alias,
+ Table = table,
+ NavPath = navPathArray
+ });
+ }
+ var sbfield = new StringBuilder();
+ if (string.IsNullOrEmpty(_field) == false) sbfield.Append(", ").Append(_field);
+ foreach (var col in table.Columns.Values)
+ {
+ var colName = col.Attribute.Name;
+ for (var a = 2; true; a++)
+ {
+ if (_fieldAlias.ContainsKey(colName)) colName = $"{col.Attribute.Name}{a}";
+ else break;
+ }
+ _fieldAlias.Add(colName, col.Attribute.Name);
+ sbfield.Append(", ").Append(alias).Append(".").Append(_common.QuoteSqlName(col.Attribute.Name));
+ if (colName != col.Attribute.Name) sbfield.Append(_common.FieldAsAlias(colName));
+ }
+ _field = sbfield.Remove(0, 2).ToString();
+ sbfield.Clear();
+
+ _fieldReader.Add(dr =>
+ {
+ var pkIsNull = false;
+ foreach (var col in table.Columns.Values)
+ {
+ if (pkIsNull == false && col.Attribute.IsPrimary)
+ {
+ pkIsNull = dr.Reader.IsDBNull(dr.Index);
+ if (pkIsNull) dr.Result.Clear();
+ }
+ if (pkIsNull == false) dr.Result[col.CsName] = Utils.GetDataReaderValue(col.CsType, dr.Reader.GetValue(dr.Index));
+ dr.Index++;
+ }
+ });
+ return tableAlias;
+ }
+ T FetchResult(DbDataReader reader)
+ {
+ var fieldIndex = 0;
+ var result = new T();
+ for (var aliasIndex = 0; aliasIndex < _tableAlias.Count; aliasIndex++)
+ {
+ var navValue = result;
+ var drctx = new DbDataReaderContext { Index = fieldIndex, Reader = reader };
+ if (aliasIndex == 0)
+ {
+ drctx.Result = result;
+ }
+ else
+ {
+ var isNull = false;
+ for (var navidx = 0; navidx < _tableAlias[aliasIndex].NavPath.Length - 1; navidx++)
+ {
+ var navKey = _tableAlias[aliasIndex].NavPath[navidx];
+ if (navValue.ContainsKey(navKey) == false)
+ {
+ isNull = true;
+ break;
+ }
+ navValue = navValue[navKey] as T;
+ if (navValue == null)
+ {
+ isNull = true;
+ break;
+ }
+ }
+ if (isNull)
+ {
+ fieldIndex += _tableAlias[aliasIndex].Table.Columns.Count;
+ continue;
+ }
+ drctx.Result = new T();
+ navValue[_tableAlias[aliasIndex].NavPath.LastOrDefault()] = drctx.Result;
+ }
+ _fieldReader[aliasIndex](drctx);
+ fieldIndex = drctx.Index;
+ if (aliasIndex > 0 && drctx.Result.Any() == false) navValue.Remove(_tableAlias[aliasIndex].NavPath.LastOrDefault());
+ }
+ return result;
+ }
+
+ void IncludeMany(TableAliasInfo alias, NativeTuple> navMany, List list, List flagIndexs = null)
+ {
+ if (list?.Any() != true) return;
+ if (flagIndexs == null) flagIndexs = new List();
+ flagIndexs.Add(alias.Table.CsName);
+
+ var nav = alias.Table.Navigates[navMany.Item1];
+ if (_includeAll && flagIndexs.Contains(nav.RefTable.CsName)) return;
+
+ if (nav.RefType == TableRefType.OneToMany)
+ {
+ var subTable = nav.RefTable;
+ var subSelect = new SelectImpl(_dbcontext, subTable.CsName);
+ if (_includeAll) subSelect.IncludeAll();
+
+ Func> getWhereDic = () =>
+ {
+ var sbDic = new Dictionary();
+ for (var y = 0; y < list.Count; y++)
+ {
+ var sbWhereOne = new StringBuilder();
+ sbWhereOne.Append("(");
+ for (var z = 0; z < nav.Columns.Count; z++)
+ {
+ if (z > 0) sbWhereOne.Append(" AND ");
+ var refcol = nav.RefTable.ColumnsByCs[nav.RefColumns[z]];
+ var val = Utils.GetDataReaderValue(refcol.Attribute.MapType, list[y][nav.Columns[z]]);
+ sbWhereOne.Append(_common.FormatSql($"a.{_common.QuoteSqlName(refcol.Attribute.Name)}={{0}}", val));
+ }
+ sbWhereOne.Append(")");
+ var whereOne = sbWhereOne.ToString();
+ sbWhereOne.Clear();
+ if (sbDic.ContainsKey(whereOne) == false) sbDic.Add(whereOne, true);
+ }
+ return sbDic;
+ };
+ if (nav.Columns.Count == 1)
+ {
+ var refcol = nav.RefTable.ColumnsByCs[nav.RefColumns[0]];
+ var args1 = $"a.{_common.QuoteSqlName(refcol.Attribute.Name)}";
+ var left = _common.FormatSql("{0}", new object[] { list.Select(a => a[nav.Columns[0]]).Distinct().ToArray() });
+ subSelect._select.Where($"({args1} in {left.Replace(", \r\n \r\n", $") \r\n OR {args1} in (")})");
+ }
+ else
+ {
+ var sbDic = getWhereDic();
+ var sbWhere = new StringBuilder();
+ foreach (var sbd in sbDic)
+ sbWhere.Append(" OR ").Append(sbd.Key);
+ subSelect._select.Where(sbWhere.Remove(0, 4).ToString());
+ sbWhere.Clear();
+ sbDic.Clear();
+ }
+ navMany.Item2?.Invoke(subSelect);
+ var subList = subSelect.ToListPrivate(null, null, flagIndexs);
+ foreach (var item in list)
+ {
+ item[nav.NavigateKey] = subList.Where(a =>
+ {
+ for (var z = 0; z < nav.Columns.Count; z++)
+ if (CompareEntityPropertyValue(nav.Table.ColumnsByCs[nav.Columns[z]].Attribute.MapType, item[nav.Columns[z]], a[nav.RefColumns[z]]) == false)
+ return false;
+ return true;
+ }).ToList();
+ }
+ subList.Clear();
+ }
+ else if (nav.RefType == TableRefType.ManyToMany)
+ {
+ var subTable = nav.RefTable;
+ var subSelect = new SelectImpl(_dbcontext, subTable.CsName);
+ if (_includeAll) subSelect.IncludeAll();
+
+ var middleJoinOn = string.Join(" AND ", nav.RefColumns.Select((bname, idx) =>
+ $"midtb.{_common.QuoteSqlName(nav.RefMiddleTable.ColumnsByCs[nav.MiddleColumns[nav.Columns.Count + idx]].Attribute.Name)} = a.{_common.QuoteSqlName(nav.RefTable.ColumnsByCs[bname].Attribute.Name)}"));
+ subSelect._select.InnerJoin($"{_common.QuoteSqlName(nav.RefMiddleTable.DbName)} midtb ON {middleJoinOn}");
+
+ Func> getWhereDic = () =>
+ {
+ var sbDic = new Dictionary();
+ for (var y = 0; y < list.Count; y++)
+ {
+ var sbWhereOne = new StringBuilder();
+ sbWhereOne.Append("(");
+ for (var z = 0; z < nav.Columns.Count; z++)
+ {
+ if (z > 0) sbWhereOne.Append(" AND ");
+ var midcol = nav.RefMiddleTable.ColumnsByCs[nav.MiddleColumns[z]];
+ var val = Utils.GetDataReaderValue(midcol.Attribute.MapType, list[y][nav.Columns[z]]);
+ sbWhereOne.Append(_common.FormatSql($"midtb.{_common.QuoteSqlName(midcol.Attribute.Name)}={{0}}", val));
+ }
+ sbWhereOne.Append(")");
+ var whereOne = sbWhereOne.ToString();
+ sbWhereOne.Clear();
+ if (sbDic.ContainsKey(whereOne) == false) sbDic.Add(whereOne, true);
+ }
+ return sbDic;
+ };
+ if (nav.Columns.Count == 1)
+ {
+ var midcol = nav.RefMiddleTable.ColumnsByCs[nav.MiddleColumns[0]];
+ var args1 = $"midtb.{_common.QuoteSqlName(midcol.Attribute.Name)}";
+ var left = _common.FormatSql("{0}", new object[] { list.Select(a => a[nav.Columns[0]]).Distinct().ToArray() });
+ subSelect._select.Where($"({args1} in {left.Replace(", \r\n \r\n", $") \r\n OR {args1} in (")})");
+ }
+ else
+ {
+ var sbDic = getWhereDic();
+ var sbWhere = new StringBuilder();
+ foreach (var sbd in sbDic)
+ sbWhere.Append(" OR ").Append(sbd.Key);
+ subSelect._select.Where(sbWhere.Remove(0, 4).ToString());
+ sbWhere.Clear();
+ sbDic.Clear();
+ }
+ navMany.Item2?.Invoke(subSelect);
+ var subList = subSelect.ToListPrivate(
+ string.Join(", ", nav.MiddleColumns.Select((a, idx) => $"midtb.{_common.QuoteSqlName(nav.RefMiddleTable.ColumnsByCs[a].Attribute.Name)}{_common.FieldAsAlias($"midtb_field__{idx}")}")),
+ (dict, dr) =>
+ {
+ var fieldCount = dr.FieldCount - nav.MiddleColumns.Count;
+ for (var z = 0; z < nav.MiddleColumns.Count; z++)
+ dict[$"midtb_field__{z}"] = Utils.GetDataReaderValue(nav.RefMiddleTable.ColumnsByCs[nav.MiddleColumns[z]].CsType, dr.GetValue(fieldCount + z));
+ }, flagIndexs);
+ foreach (var item in list)
+ {
+ item[nav.NavigateKey] = subList.Where(a =>
+ {
+ for (var z = 0; z < nav.Columns.Count; z++)
+ if (CompareEntityPropertyValue(nav.Table.ColumnsByCs[nav.Columns[z]].Attribute.MapType, item[nav.Columns[z]], a[$"midtb_field__{z}"]) == false)
+ return false;
+ return true;
+ }).ToList();
+ }
+ foreach (var subItem in subList)
+ for (var z = 0; z < nav.MiddleColumns.Count; z++)
+ subItem.Remove($"midtb_field__{z}");
+ subList.Clear();
+ }
+ }
+
+ public string ToSql(string field = null)
+ {
+ if (string.IsNullOrWhiteSpace(field)) return _select.ToSql(_field);
+ return _select.ToSql(field);
+ }
+ List ToListPrivate(string otherField, Action otherReader, List flagIndexs = null)
+ {
+ var sql = string.IsNullOrWhiteSpace(otherField) ? this.ToSql() : this.ToSql($"{_field},{otherField}");
+ var ret = new List();
+ var dbParms = _params.ToArray();
+ var before = new Aop.CurdBeforeEventArgs(_tables[_mainTableIndex].Type, _tables[_mainTableIndex], Aop.CurdType.Select, sql, dbParms);
+ _orm.Aop.CurdBeforeHandler?.Invoke(this, before);
+ Exception exception = null;
+ try
+ {
+ _orm.Ado.ExecuteReader(_dbcontext._transaction?.Connection, _dbcontext._transaction, fetch =>
+ {
+ var item = FetchResult(fetch.Object);
+ otherReader?.Invoke(item, fetch.Object);
+ ret.Add(item);
+ }, CommandType.Text, sql, _dbcontext._commandTimeout, dbParms);
+ }
+ catch (Exception ex)
+ {
+ exception = ex;
+ throw;
+ }
+ finally
+ {
+ var after = new Aop.CurdAfterEventArgs(before, exception, ret);
+ _orm.Aop.CurdAfterHandler?.Invoke(this, after);
+ }
+ foreach (var join in _tableAlias)
+ {
+ if (join.IncludeMany.Any() == false) continue;
+ var list = new List();
+ if (join.Alias == "a") list = ret;
+ else
+ {
+ foreach (var obj in ret)
+ {
+ T item = obj;
+ foreach (var navKey in join.NavPath)
+ {
+ if (string.IsNullOrWhiteSpace(navKey)) continue;
+ item.TryGetValue(navKey, out var obj2);
+ item = obj2 as T;
+ if (item == null) break;
+ }
+ if (item != null) list.Add(item);
+ }
+ }
+ foreach(var navMany in join.IncludeMany)
+ IncludeMany(join, navMany, list, flagIndexs);
+ }
+ if (_useStates && flagIndexs == null)
+ foreach (var item in ret)
+ _dbcontext.AttachCascade(_tables[_mainTableIndex], item, true);
+ return ret;
+ }
+ public List ToList() => ToListPrivate(null, null);
+ public T ToOne() => ToListPrivate(null, null).FirstOrDefault();
+ public T First() => ToOne();
+ public bool Any() => _select.Any();
+ public long Count() => _select.Count();
+ public SelectImpl Count(out long count)
+ {
+ _select.Count(out count);
+ return this;
+ }
+ public SelectImpl WithTransaction(DbTransaction transaction)
+ {
+ _select.WithTransaction(transaction);
+ return this;
+ }
+ public SelectImpl WithConnection(DbConnection connection)
+ {
+ _select.WithConnection(connection);
+ return this;
+ }
+ public SelectImpl CommandTimeout(int timeout)
+ {
+ _select.CommandTimeout(timeout);
+ return this;
+ }
+ public SelectImpl Distinct()
+ {
+ _select.Distinct();
+ return this;
+ }
+ public SelectImpl Master()
+ {
+ _select.Master();
+ return this;
+ }
+ public SelectImpl ForUpdate(bool nowait = false)
+ {
+ _select.ForUpdate(nowait);
+ return this;
+ }
+
+ NativeTuple ParseField(ZoreTableInfo firstTable, string firstTableAlias, string property)
+ {
+ if (string.IsNullOrEmpty(property)) return null;
+ var field = property.Split('.').Select(a => a.Trim()).ToArray();
+
+ if (field.Length == 1)
+ {
+ if (firstTable != null && firstTable.ColumnsByCs.TryGetValue(field[0], out var col2) == true)
+ return NativeTuple.Create($"{firstTableAlias}.{_common.QuoteSqlName(col2.Attribute.Name)}", col2);
+
+ foreach (var ta2 in _tableAlias)
+ {
+ if (ta2.Table.ColumnsByCs.TryGetValue(field[0], out col2))
+ return NativeTuple.Create($"{ta2.Alias}.{_common.QuoteSqlName(col2.Attribute.Name)}", col2);
+ }
+ }
+ else if (field.Length == 2)
+ {
+ if (firstTable != null && firstTable.CsName.ToLower() == field[0].ToLower() && firstTable.ColumnsByCs.TryGetValue(field[1], out var col2) == true)
+ return NativeTuple.Create($"{firstTableAlias}.{_common.QuoteSqlName(col2.Attribute.Name)}", col2);
+
+ var ta2s = _tableAlias.Where(a => a.Table.CsName.ToLower() == field[0].ToLower()).ToArray();
+ if (ta2s.Length == 1 && ta2s[0].Table.ColumnsByCs.TryGetValue(field[1], out col2) == true)
+ return NativeTuple.Create($"{ta2s[0].Alias}.{_common.QuoteSqlName(col2.Attribute.Name)}", col2);
+ if (ta2s.Length > 1)
+ {
+ ta2s = _tableAlias.Where(a => a.Table.CsName == field[0]).ToArray();
+ if (ta2s.Length == 1 && ta2s[0].Table.ColumnsByCs.TryGetValue(field[1], out col2) == true)
+ return NativeTuple.Create($"{ta2s[0].Alias}.{_common.QuoteSqlName(col2.Attribute.Name)}", col2);
+ }
+ if (_tableAlias.Where(a => a.Alias == field[0]).FirstOrDefault()?.Table.ColumnsByCs.TryGetValue(field[1], out col2) == true)
+ return NativeTuple.Create($"{field[0]}.{_common.QuoteSqlName(col2.Attribute.Name)}", col2);
+ }
+
+ var navPath = string.Join(".", field.Skip(1).Take(field.Length - 1));
+ var ta = _tableAlias.Where(a => string.Join(".", a.NavPath) == navPath).FirstOrDefault();
+ if (ta?.Table.ColumnsByCs.TryGetValue(field.Last(), out var col) == true)
+ return NativeTuple.Create($"{ta.Alias}.{_common.QuoteSqlName(col.Attribute.Name)}", col);
+ throw new Exception(CoreStrings.Cannot_Match_Property(property));
+ }
+
+ ///
+ /// WHERE [Id] IN (...)
+ ///
+ public SelectImpl Where(IEnumerable items)
+ {
+ var alias = _tableAlias.Where(a => a.Table == _tables[_mainTableIndex]).FirstOrDefault()?.Alias;
+ if (!string.IsNullOrWhiteSpace(alias)) alias = $"{alias}.";
+ var where = _common.WhereItems(_tables[_mainTableIndex].Primarys, alias, items);
+ _select.Where(where);
+ return this;
+ }
+ ///
+ /// Where(new { Year = 2017, CategoryId = 198, IsPublished = true })
+ /// WHERE [Year] = 2017 AND [CategoryId] = 198 AND [IsPublished] = 1
+ ///
+ public SelectImpl Where(object multipleFields)
+ {
+ if (multipleFields == null) return this;
+ foreach (var prop in multipleFields.GetType().GetProperties())
+ WhereDynamicFilter(new DynamicFilterInfo { Field = prop.Name, Operator = DynamicFilterOperator.Eq, Value = prop.GetValue(multipleFields, null) });
+ return this;
+ }
+ ///
+ /// WHERE [field] = ..
+ ///
+ public SelectImpl Where(string field, object value) => WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.Eq, Value = value });
+ public SelectImpl Where(string field, string @operator, object value)
+ {
+ switch (@operator?.ToLower().Trim())
+ {
+ case "=":
+ case "==":
+ case "eq":
+ return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.Eq, Value = value });
+ case "!=":
+ case "<>":
+ return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.NotEqual, Value = value });
+ case ">":
+ return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.GreaterThan, Value = value });
+ case ">=":
+ return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.GreaterThanOrEqual, Value = value });
+ case "<":
+ return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.LessThan, Value = value });
+ case "<=":
+ return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.LessThanOrEqual, Value = value });
+ case "like":
+ case "contains":
+ return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.Contains, Value = value });
+ case "!like":
+ case "notlike":
+ case "not like":
+ return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.NotContains, Value = value });
+ case "in":
+ return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.Any, Value = value });
+ case "!in":
+ case "notin":
+ case "not in":
+ return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.Any, Value = value });
+ }
+ throw new Exception($"未实现 {@operator}");
+ }
+ public SelectImpl WhereColumns(string field1, string @operator, string field2)
+ {
+ var field1Result = ParseField(null, null, field1);
+ if (field1Result == null) throw new Exception($"未匹配字段名 {field1}");
+ var field2Result = ParseField(null, null, field2);
+ if (field2Result == null) throw new Exception($"未匹配字段名 {field2}");
+ switch (@operator?.ToLower().Trim())
+ {
+ case "=":
+ case "==":
+ case "eq":
+ _select.Where($"{field1Result.Item1} = {field2Result.Item1}");
+ return this;
+ case "!=":
+ case "<>":
+ _select.Where($"{field1Result.Item1} <> {field2Result.Item1}");
+ return this;
+ case ">":
+ _select.Where($"{field1Result.Item1} > {field2Result.Item1}");
+ return this;
+ case ">=":
+ _select.Where($"{field1Result.Item1} >= {field2Result.Item1}");
+ return this;
+ case "<":
+ _select.Where($"{field1Result.Item1} < {field2Result.Item1}");
+ return this;
+ case "<=":
+ _select.Where($"{field1Result.Item1} <= {field2Result.Item1}");
+ return this;
+ }
+ throw new Exception($"未实现 {@operator}");
+ }
+ public SelectImpl WhereDynamicFilter(DynamicFilterInfo filter)
+ {
+ var sql = ParseDynamicFilter(filter);
+ _selectProvider._where.Append(sql);
+ return this;
+ }
+ string ParseDynamicFilter(DynamicFilterInfo filter)
+ {
+ var replacedFilter = new DynamicFilterInfo();
+ var replacedMap = new List>();
+ LocalCloneFilter(filter, replacedFilter);
+ var oldWhere = _selectProvider._where.ToString();
+ var newWhere = "";
+ try
+ {
+ _selectProvider._where.Clear();
+ _select.WhereDynamicFilter(replacedFilter);
+ newWhere = _selectProvider._where.ToString();
+ }
+ finally
+ {
+ _selectProvider._where.Clear().Append(oldWhere);
+ }
+ foreach (var rm in replacedMap)
+ {
+ var find = $"a.{_common.QuoteSqlName(rm.Item1)}";
+ var idx = newWhere.IndexOf(find);
+ if (idx != -1) newWhere = $"{newWhere.Substring(0, idx)}{rm.Item2}{newWhere.Substring(idx + find.Length)}";
+ }
+ return newWhere;
+
+ void LocalCloneFilter(DynamicFilterInfo source, DynamicFilterInfo target)
+ {
+ target.Field = source.Field;
+ target.Operator = source.Operator;
+ target.Value = source.Value;
+ target.Logic = source.Logic;
+ if (string.IsNullOrWhiteSpace(source.Field) == false)
+ {
+ var parseResult = ParseField(null, null, source.Field);
+ if (parseResult != null)
+ {
+ if (TestDynamicFilterInfo._dictTypeToPropertyname.TryGetValue(parseResult.Item2.Attribute.MapType, out var pname))
+ target.Field = pname;
+ else
+ target.Field = TestDynamicFilterInfo._dictTypeToPropertyname[typeof(string)];
+ replacedMap.Add(NativeTuple.Create(target.Field, parseResult.Item1));
+ }
+ }
+ if (source.Filters?.Any() == true)
+ {
+ target.Filters = new List();
+ foreach (var sourceChild in source.Filters)
+ {
+ var targetChild = new DynamicFilterInfo();
+ target.Filters.Add(targetChild);
+ LocalCloneFilter(sourceChild, targetChild);
+ }
+ }
+ }
+ }
+ public class SubQuery
+ {
+ internal SelectImpl _parentQuery;
+ internal SubQuery() { }
+ public SelectImpl From(string tableName)
+ {
+ var query = _parentQuery._dbcontext.SelectNoTracking(tableName);
+ query._selectProvider._tables[0].Alias =
+ query._tableAlias[0].Alias = $"sub_{_parentQuery._tableAlias[0].Alias}";
+ query._tableAlias.AddRange(_parentQuery._tableAlias);
+ return query;
+ }
+ }
+ public SelectImpl WhereExists(Func q)
+ {
+ var query = q?.Invoke(new SubQuery { _parentQuery = this });
+ switch (_orm.Ado.DataType)
+ {
+ case DataType.Oracle:
+ case DataType.OdbcOracle:
+ case DataType.CustomOracle:
+ case DataType.Dameng:
+ case DataType.OdbcDameng:
+ case DataType.GBase:
+ query.Limit(-1);
+ break;
+ default:
+ query.Limit(1); //#462 ORACLE rownum <= 2 会影响索引变慢
+ break;
+ }
+ _selectProvider._where.Append($" AND EXISTS({query.ToSql("1").Replace(" \r\n", " \r\n ")})");
+ return this;
+ }
+ public SelectImpl GroupByRaw(string sql)
+ {
+ if (string.IsNullOrWhiteSpace(sql)) return this;
+ _useStates = false;
+ _groupBy = $"{_groupBy}, {sql}";
+ _select.GroupBy(_groupBy);
+ return this;
+ }
+ public SelectImpl GroupBy(string[] fields)
+ {
+ var count = 0;
+ for (var a = 0; a < fields.Length; a++)
+ {
+ if (string.IsNullOrWhiteSpace(fields[a])) continue;
+ var field1 = ParseField(null, null, fields[a]);
+ if (field1 == null) throw new Exception($"未匹配字段名 {fields[a]}");
+ _groupBy = $"{_groupBy}, {field1.Item1}";
+ count++;
+ }
+ if (count > 0)
+ {
+ _useStates = false;
+ _select.GroupBy(_groupBy);
+ }
+ return this;
+ }
+ public SelectImpl HavingRaw(string sql)
+ {
+ _select.Having(sql);
+ return this;
+ }
+ public SelectImpl OrderByRaw(string sql)
+ {
+ _select.OrderBy(sql);
+ return this;
+ }
+ SelectImpl OrderBy(bool isdesc, string[] fields)
+ {
+ for (var a = 0; a < fields.Length; a ++)
+ {
+ if (string.IsNullOrWhiteSpace(fields[a])) continue;
+ var field1 = ParseField(null, null, fields[a]);
+ if (field1 == null) throw new Exception($"未匹配字段名 {fields[a]}");
+ if (isdesc) _select.OrderBy($"{field1.Item1} DESC");
+ else _select.OrderBy(field1.Item1);
+ }
+ return this;
+ }
+ public SelectImpl OrderBy(params string[] fields) => OrderBy(false, fields);
+ public SelectImpl OrderByDescending(params string[] fields) => OrderBy(true, fields);
+ public SelectImpl Offset(int offset)
+ {
+ _select.Offset(offset);
+ return this;
+ }
+ public SelectImpl Limit(int limit)
+ {
+ _select.Limit(limit);
+ return this;
+ }
+ public SelectImpl Skip(int offset) => Offset(offset);
+ public SelectImpl Take(int limit) => Limit(limit);
+ public SelectImpl Page(int pageNumber, int pageSize)
+ {
+ _select.Page(pageNumber, pageSize);
+ return this;
+ }
+
+ TResult InternalQuerySingle(string field) => _orm.Ado.CommandFluent(this.ToSql(field))
+ .WithConnection(_selectProvider._connection)
+ .WithTransaction(_selectProvider._transaction).QuerySingle();
+ public decimal Sum(string field)
+ {
+ var field1 = ParseField(null, null, field);
+ if (field1 == null) throw new Exception($"未匹配字段名 {field}");
+ return InternalQuerySingle($"sum({field1.Item1})");
+ }
+ public TMember Min(string field)
+ {
+ var field1 = ParseField(null, null, field);
+ if (field1 == null) throw new Exception($"未匹配字段名 {field}");
+ return InternalQuerySingle($"min({field1.Item1})");
+ }
+ public TMember Max(string field)
+ {
+ var field1 = ParseField(null, null, field);
+ if (field1 == null) throw new Exception($"未匹配字段名 {field}");
+ return InternalQuerySingle($"max({field1.Item1})");
+ }
+ public double Avg(string field)
+ {
+ var field1 = ParseField(null, null, field);
+ if (field1 == null) throw new Exception($"未匹配字段名 {field}");
+ return InternalQuerySingle($"avg({field1.Item1})");
+ }
+
+
+ [Table(DisableSyncStructure = true)]
+ class TestDynamicFilterInfo
+ {
+ public Guid DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf00 { get; set; }
+ public bool DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf01 { get; set; }
+ public string DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf02 { get; set; }
+ public char DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf03 { get; set; }
+ public DateTime DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf04 { get; set; }
+ public DateTimeOffset DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf05 { get; set; }
+ public TimeSpan DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf06 { get; set; }
+
+ public int DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf07 { get; set; }
+ public long DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf08 { get; set; }
+ public short DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf09 { get; set; }
+ public sbyte DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf10 { get; set; }
+
+ public uint DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf11 { get; set; }
+ public ulong DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf12 { get; set; }
+ public ushort DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf13 { get; set; }
+ public byte DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf14 { get; set; }
+ public byte[] DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf15 { get; set; }
+
+ public double DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf16 { get; set; }
+ public float DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf17 { get; set; }
+ public decimal DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf18 { get; set; }
+
+ internal static Dictionary _dictTypeToPropertyname = new Dictionary
+ {
+ [typeof(Guid)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf00),
+ [typeof(bool)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf01),
+ [typeof(string)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf02),
+ [typeof(char)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf03),
+ [typeof(DateTime)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf04),
+ [typeof(DateTimeOffset)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf05),
+ [typeof(TimeSpan)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf06),
+
+ [typeof(int)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf07),
+ [typeof(long)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf08),
+ [typeof(short)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf09),
+ [typeof(sbyte)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf10),
+
+ [typeof(uint)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf11),
+ [typeof(ulong)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf12),
+ [typeof(ushort)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf13),
+ [typeof(byte)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf14),
+ [typeof(byte[])] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf15),
+
+ [typeof(double)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf16),
+ [typeof(float)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf17),
+ [typeof(decimal)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf18),
+ };
+ }
+ }
+ }
+}
diff --git a/Extensions/FreeSql.Extensions.ZoreEntity/ZoreDbContext.cs b/Extensions/FreeSql.Extensions.ZoreEntity/ZoreDbContext.cs
new file mode 100644
index 00000000..2cf5d95f
--- /dev/null
+++ b/Extensions/FreeSql.Extensions.ZoreEntity/ZoreDbContext.cs
@@ -0,0 +1,1476 @@
+using FreeSql.DataAnnotations;
+using FreeSql.Internal;
+using FreeSql.Internal.CommonProvider;
+using FreeSql.Internal.Model;
+using System;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Data.Common;
+using System.Linq;
+using System.Reflection;
+using T = System.Collections.Generic.Dictionary;
+
+namespace FreeSql.Extensions.ZoreEntity
+{
+
+ /*
+理解本机制之前,请先忘记 Repository/DbContext 等之前有关级联的内容,他们没有关联。
+
+schemas[] 是一组表映射信息定义,包含表名、列名、导航属性、索引等信息
+导航属性:OneToOne/OneToMany/ManyToOne/ManyToMany
+聚合根:OneToOne/OneToMany/多对多中间表,作为一个整体看待
+外部根:ManyToOne/ManyToMany外部表,作为外部看待,它有自己的聚合根整体
+User 为聚合根
+UserExt/UserClaim/UserRole 这三个表是子成员,一起存储/删除
+Role 为外部根(相对 User 而言,它自己是独立的聚合根)
+
+CURD 都是基于 schemas[0] 聚合根进行操作
+查询:贪婪加载所有子成员,以及外部根,以及外部根的外部根(递归)
+状态管理:快照聚合根副本(由于外部根也是聚合根,即外部根与聚合根是并行存储关系)
+
+对比保存:
+将当前操作的聚合根与状态管理的副本进行对比,计算出发生变化的列
+OneToOne 副本NULL 最新Object 添加新记录
+OneToOne 副本Object 最新NULL 删除副本记录
+OneToOne 副本Object 最新Object 发生变化则更新,否则忽略
+OneToMany 副本NULL/Empty 最新List 添加最新List记录
+OneToMany 副本List 最新NULL 忽略
+OneToMany 副本List 最新Empty 删除副本List记录
+OneToMany 副本List 最新List 对比保存,计算出添加/更新/删除三种行为
+多对多中间表 与 OneToMany 一致
+
+插入:
+OneToOne 级联插入
+OneToMany 级联插入
+ManyToOne 先对比保存外部根,关联外部根ID,再插入聚合根
+ManyToMany 先对比保存外部根,插入聚合根,再插入中间表
+
+更新:
+OneToOne 级联对比保存
+OneToMany 级联对比保存
+ManyToOne 先对比保存外部根,再关联外部根ID,再更新聚合根
+ManyToMany 先对比保存外部根,再更新聚合根,再对比保存中间表
+
+删除:
+OneToOne 级联删除
+OneToMany 级联删除
+ManyToOne 忽略
+ManyToMany 级联删除中间表(注意不删除外部根)
+ */
+ public partial class ZoreDbContext
+ {
+ internal IFreeSql _orm;
+ internal DbTransaction _transaction;
+ internal int _commandTimeout;
+ internal List _tables;
+ public ZoreDbContext(IFreeSql orm, TableDescriptor[] schemas)
+ {
+ _orm = orm;
+ _tables = VilidateSchemaToInfo(orm, schemas);
+ if (orm.CodeFirst.IsAutoSyncStructure)
+ foreach (var table in _tables)
+ orm.CodeFirst.SyncStructure(table, table.DbName, false);
+ }
+
+ static List VilidateSchemaToInfo(IFreeSql orm, IEnumerable schemas)
+ {
+ var common = (orm.Ado as AdoProvider)._util;
+ var tables = new List();
+ foreach (var dtd in schemas)
+ {
+ if (string.IsNullOrWhiteSpace(dtd.Name)) continue;
+ if (string.IsNullOrWhiteSpace(dtd.DbName)) dtd.DbName = dtd.Name;
+ var tabattr = new TableAttribute
+ {
+ Name = dtd.DbName,
+ AsTable = dtd.AsTable,
+ };
+ var tabindexs = dtd.Indexes.Select(a => new IndexAttribute(a.Name, a.Fields, a.IsUnique)
+ {
+ IndexMethod = a.IndexMethod,
+ });
+ var tab = new ZoreTableInfo();
+ tab.Comment = dtd.Comment;
+ tab.Type = typeof(object);
+ tab.CsName = dtd.Name;
+ tab.DbName = dtd.DbName;
+ var isQuery = tab.DbName.StartsWith("(") && tab.DbName.EndsWith(")");
+
+ if (isQuery == false)
+ {
+ if (orm.CodeFirst.IsSyncStructureToLower) tab.DbName = tab.DbName.ToLower();
+ if (orm.CodeFirst.IsSyncStructureToUpper) tab.DbName = tab.DbName.ToUpper();
+ }
+ tab.DisableSyncStructure = isQuery || dtd.DisableSyncStructure;
+ tab.IsDictionaryType = true;
+ var columnsList = new List();
+ foreach (var dtdcol in dtd.Columns)
+ {
+ if (string.IsNullOrWhiteSpace(dtdcol.Name) || dtdcol.MapType == null || tab.ColumnsByCs.ContainsKey(dtdcol.Name)) continue;
+ var tp = common.CodeFirst.GetDbInfo(dtdcol.MapType);
+ var colattr = dtdcol.ToAttribute();
+ var col = Utils.ColumnAttributeToInfo(tab, null, colattr.Name, colattr.MapType, false, ref colattr, tp, common);
+ if (col == null) continue;
+
+ col.Comment = dtdcol.Comment;
+ tab.Columns.Add(col.Attribute.Name, col);
+ tab.ColumnsByCs.Add(col.CsName, col);
+ columnsList.Add(col);
+ }
+ Utils.AuditTableInfo(tab, tabattr, tabindexs, columnsList, common);
+ columnsList.Clear();
+ tables.Add(tab);
+ }
+ var tabindex = 0;
+ foreach (var dtd in schemas)
+ {
+ var tab = tables[tabindex++];
+ foreach (var dtdnav in dtd.Navigates)
+ {
+ if (tab.Navigates.ContainsKey(dtdnav.Name)) continue;
+ var error = $"表“{tab.CsName}”导航属性 {dtdnav.Name} 配置错误:";
+ var nav = new ZoreTableRef();
+ nav.NavigateKey = dtdnav.Name;
+ nav.Table = tab;
+ nav.RefTable = tables.Where(a => a.CsName == dtdnav.RelTable).FirstOrDefault();
+ if (nav.RefTable == null) throw new Exception($"{error}未定义“{dtdnav.RelTable}”");
+
+ switch (dtdnav.Type)
+ {
+ case TableDescriptor.NavigateType.OneToOne:
+ nav.RefType = TableRefType.OneToOne;
+ nav.Columns.AddRange(nav.Table.Primarys.Select(a => a.CsName));
+ if (string.IsNullOrWhiteSpace(dtdnav.Bind))
+ nav.RefColumns.AddRange(nav.RefTable.Primarys.Select(a => a.CsName));
+ else
+ nav.RefColumns.AddRange(dtdnav.Bind.Split(',')
+ .Select(a => nav.RefTable.ColumnsByCs.TryGetValue(a.Trim(), out var refcol) ? refcol.CsName : "")
+ .Where(a => string.IsNullOrWhiteSpace(a) == false));
+ break;
+ case TableDescriptor.NavigateType.ManyToOne:
+ nav.RefType = TableRefType.ManyToOne;
+ nav.Columns.AddRange(dtdnav.Bind.Split(',')
+ .Select(a => nav.Table.ColumnsByCs.TryGetValue(a.Trim(), out var refcol) ? refcol.CsName : "")
+ .Where(a => string.IsNullOrWhiteSpace(a) == false));
+ nav.RefColumns.AddRange(nav.RefTable.Primarys.Select(a => a.CsName));
+ break;
+ case TableDescriptor.NavigateType.OneToMany:
+ nav.RefType = TableRefType.OneToMany;
+ nav.Columns.AddRange(nav.Table.Primarys.Select(a => a.CsName));
+ nav.RefColumns.AddRange(dtdnav.Bind.Split(',')
+ .Select(a => nav.RefTable.ColumnsByCs.TryGetValue(a.Trim(), out var refcol) ? refcol.CsName : "")
+ .Where(a => string.IsNullOrWhiteSpace(a) == false));
+ break;
+ case TableDescriptor.NavigateType.ManyToMany:
+ nav.RefType = TableRefType.ManyToMany;
+ var midtab = tables.Where(a => a.CsName == dtdnav.ManyToMany).FirstOrDefault();
+ nav.RefMiddleTable = midtab;
+ if (nav.RefMiddleTable == null) throw new Exception($"{error}ManyToMany未定义“{dtdnav.ManyToMany}”");
+ var midtabRaw = schemas.Where(a => a.Name == midtab.CsName).FirstOrDefault();
+ var midTabNav1 = midtabRaw.Navigates.Where(a => a.Type == TableDescriptor.NavigateType.ManyToOne && a.RelTable == nav.Table.CsName).FirstOrDefault();
+ if (midTabNav1 == null) throw new Exception($"{error}ManyToMany中间表“{dtdnav.ManyToMany}”没有与表“{nav.Table.CsName}”形成 ManyToOne 关联");
+ var midTabNav1Columns = midTabNav1.Bind.Split(',')
+ .Select(a => midtab.ColumnsByCs.TryGetValue(a.Trim(), out var refcol) ? refcol.CsName : "")
+ .Where(a => string.IsNullOrWhiteSpace(a) == false).ToArray();
+ var midTabNav2 = midtabRaw.Navigates.Where(a => a.Type == TableDescriptor.NavigateType.ManyToOne && a.RelTable == nav.RefTable.CsName).FirstOrDefault();
+ if (midTabNav2 == null) throw new Exception($"{error}ManyToMany中间表“{dtdnav.ManyToMany}”没有与表“{nav.RefTable.CsName}”形成 ManyToOne 关联");
+ var midTabNav2Columns = midTabNav2.Bind.Split(',')
+ .Select(a => midtab.ColumnsByCs.TryGetValue(a.Trim(), out var refcol) ? refcol.CsName : "")
+ .Where(a => string.IsNullOrWhiteSpace(a) == false).ToArray();
+ if (midTabNav1Columns.Length != nav.Table.Primarys.Length) throw new Exception($"{error}ManyToMany中间表“{dtdnav.ManyToMany}”关联字段的数目不相等");
+ if (midTabNav1Columns.Where((a, idx) => midtab.ColumnsByCs[a].CsType != nav.Table.Primarys[idx].CsType).Any()) throw new Exception($"{error}ManyToMany中间表“{dtdnav.ManyToMany}”关联字段的类型不相等");
+ if (midTabNav2Columns.Length != nav.RefTable.Primarys.Length) throw new Exception($"{error}ManyToMany中间表“{dtdnav.ManyToMany}”与表“{nav.RefTable.CsName}”关联字段的数目不相等");
+ if (midTabNav2Columns.Where((a, idx) => midtab.ColumnsByCs[a].CsType != nav.RefTable.Primarys[idx].CsType).Any()) throw new Exception($"{error}ManyToMany中间表“{dtdnav.ManyToMany}”与表“{nav.RefTable.CsName}”关联字段的类型不相等");
+ nav.Columns.AddRange(nav.Table.Primarys.Select(a => a.CsName));
+ nav.MiddleColumns.AddRange(midTabNav1Columns);
+ nav.MiddleColumns.AddRange(midTabNav2Columns);
+ nav.RefColumns.AddRange(nav.RefTable.Primarys.Select(a => a.CsName));
+ break;
+ }
+ switch (dtdnav.Type)
+ {
+ case TableDescriptor.NavigateType.OneToOne:
+ case TableDescriptor.NavigateType.ManyToOne:
+ case TableDescriptor.NavigateType.OneToMany:
+ if (nav.Columns.Any() == false || nav.Columns.Count != nav.RefColumns.Count) throw new Exception($"{error}与表“{dtdnav.RelTable}”关联字段的数目不相等");
+ if (nav.Columns.Where((a, idx) => nav.Table.ColumnsByCs[a].CsType != nav.RefTable.ColumnsByCs[nav.RefColumns[idx]].CsType).Any()) throw new Exception($"{error}与表“{dtdnav.RelTable}”关联字段的类型不匹配");
+ break;
+ }
+ tab.Navigates.Add(dtdnav.Name, nav);
+ }
+ }
+ return tables;
+ }
+
+ public ZoreDbContext WithTransaction(DbTransaction value)
+ {
+ _transaction = value;
+ return this;
+ }
+ public ZoreDbContext CommandTimeout(int seconds)
+ {
+ _commandTimeout = seconds;
+ return this;
+ }
+ void TransactionInvoke(Action handler)
+ {
+ if (_transaction == null)
+ {
+ var threadTransaction = _orm.Ado.TransactionCurrentThread;
+ if (threadTransaction != null) this.WithTransaction(threadTransaction);
+ }
+ if (_transaction != null)
+ {
+ handler?.Invoke();
+ return;
+ }
+ using (var conn = _orm.Ado.MasterPool.Get())
+ {
+ _transaction = conn.Value.BeginTransaction();
+ var transBefore = new Aop.TraceBeforeEventArgs("BeginTransaction", null);
+ try
+ {
+ _orm.Aop.TraceBeforeHandler?.Invoke(this, transBefore);
+ handler?.Invoke();
+ _transaction.Commit();
+ _orm.Aop.TraceAfterHandler?.Invoke(this, new Aop.TraceAfterEventArgs(transBefore, CoreStrings.Commit, null));
+ }
+ catch (Exception ex)
+ {
+ _transaction.Rollback();
+ _orm.Aop.TraceAfterHandler?.Invoke(this, new Aop.TraceAfterEventArgs(transBefore, CoreStrings.RollBack, ex));
+ throw;
+ }
+ finally
+ {
+ _transaction = null;
+ }
+ }
+ }
+ ///
+ /// 【有状态管理】自动 Include 查询
+ ///
+ public SelectImpl Select => new SelectImpl(this, _tables[0].CsName).IncludeAll();
+ ///
+ /// 【无状态管理】指定表查询
+ ///
+ public SelectImpl SelectNoTracking(string tableName) => new SelectImpl(this, tableName).NoTracking();
+
+ public int Insert(T entity) => Insert(new[] { entity });
+ public int Insert(IEnumerable entities)
+ {
+ _cascadeAffrows = 0;
+ _cascadeIgnores.Clear();
+ try
+ {
+ TransactionInvoke(() =>
+ {
+ AuditCascade(_tables[0], entities);
+ InsertCascade(_tables[0], entities, true);
+ });
+ return _cascadeAffrows;
+ }
+ finally
+ {
+ _cascadeAffrows = 0;
+ _cascadeIgnores.Clear();
+ }
+ }
+ public int Update(T entity) => Update(new[] { entity });
+ public int Update(IEnumerable entities)
+ {
+ _cascadeAffrows = 0;
+ _cascadeIgnores.Clear();
+ try
+ {
+ TransactionInvoke(() =>
+ {
+ AuditCascade(_tables[0], entities);
+ UpdateCascade(_tables[0], entities, true);
+ });
+ return _cascadeAffrows;
+ }
+ finally
+ {
+ _cascadeAffrows = 0;
+ _cascadeIgnores.Clear();
+ }
+ }
+ public int Delete(T entity) => Delete(new[] { entity });
+ public int Delete(IEnumerable entities)
+ {
+ _cascadeAffrows = 0;
+ _cascadeIgnores.Clear();
+ try
+ {
+ TransactionInvoke(() =>
+ {
+ AuditCascade(_tables[0], entities);
+ DeleteCascade(_tables[0], entities, null);
+ });
+ return _cascadeAffrows;
+ }
+ finally
+ {
+ _cascadeAffrows = 0;
+ _cascadeIgnores.Clear();
+ }
+ }
+ public void FlushState()
+ {
+ _states.Clear();
+ }
+ public void Attach(T entity)
+ {
+ AuditCascade(_tables[0], entity);
+ AttachCascade(_tables[0], entity, true);
+ }
+
+ void AuditCascade(ZoreTableInfo entityTable, IEnumerable entities)
+ {
+ if (entities == null) return;
+ foreach (var entity in entities) AuditCascade(entityTable, entity);
+ }
+ internal void AuditCascade(ZoreTableInfo entityTable, T entity)
+ {
+ var ignores = new Dictionary>(); //比如 Tree 结构可以递归添加
+ LocalAuditCascade(entityTable, entity);
+ ignores.Clear();
+
+ void LocalAuditCascade(ZoreTableInfo table, T entityFrom)
+ {
+ if (entityFrom == null) return;
+
+ var stateKey = GetEntityKeyString(table, entityFrom);
+ if (ignores.TryGetValue(table.DbName, out var stateKeys) == false) ignores.Add(table.DbName, stateKeys = new Dictionary());
+ if (stateKey != null)
+ {
+ if (stateKeys.ContainsKey(stateKey)) return;
+ stateKeys.Add(stateKey, true);
+ }
+
+ foreach (var col in table.Columns.Values)
+ if (entityFrom.TryGetValue(col.CsName, out var colval))
+ entityFrom[col.CsName] = Utils.GetDataReaderValue(col.CsType, colval);
+
+ foreach (var nav in table.Navigates)
+ {
+ if (entityFrom.TryGetValue(nav.Key, out var propvalFrom) == false || propvalFrom == null) continue;
+ switch (nav.Value.RefType)
+ {
+ case TableRefType.OneToOne:
+ {
+ if (propvalFrom is T valFrom == false) valFrom = LocalAuditJsonElement(nv => entityFrom[nav.Key] = nv, propvalFrom) as T;
+ if (valFrom == null) continue;
+ SetNavigateRelationshipValue(nav.Value, entityFrom, valFrom);
+ LocalAuditCascade(nav.Value.RefTable, valFrom);
+ }
+ break;
+ case TableRefType.OneToMany:
+ {
+ if (propvalFrom is IEnumerable valFromList == false) valFromList = LocalAuditJsonElement(nv => entityFrom[nav.Key] = nv, propvalFrom) as IEnumerable;
+ else if (valFromList != null)
+ {
+ foreach (var fromObj in valFromList)
+ {
+ if (fromObj is T == false) valFromList = LocalAuditJsonElement(nv => entityFrom[nav.Key] = nv, propvalFrom) as IEnumerable;
+ break;
+ }
+ }
+ if (valFromList == null) continue;
+ SetNavigateRelationshipValue(nav.Value, entityFrom, valFromList);
+ foreach (var fromObj in valFromList)
+ {
+ if (fromObj is T fromItem == false || fromItem == null) continue;
+ LocalAuditCascade(nav.Value.RefTable, fromItem);
+ }
+ }
+ break;
+ case TableRefType.ManyToMany:
+ {
+ if (propvalFrom is IEnumerable valFromList == false) valFromList = LocalAuditJsonElement(nv => entityFrom[nav.Key] = nv, propvalFrom) as IEnumerable;
+ else if (valFromList != null)
+ {
+ foreach (var fromObj in valFromList)
+ {
+ if (fromObj is T == false) valFromList = LocalAuditJsonElement(nv => entityFrom[nav.Key] = nv, propvalFrom) as IEnumerable;
+ break;
+ }
+ }
+ if (valFromList == null) continue;
+ foreach (var fromObj in valFromList)
+ {
+ if (fromObj is T fromItem == false || fromItem == null) continue;
+ LocalAuditCascade(nav.Value.RefTable, fromItem);
+ }
+ }
+ break;
+ case TableRefType.ManyToOne:
+ {
+ if (propvalFrom is T valFrom == false) valFrom = LocalAuditJsonElement(nv => entityFrom[nav.Key] = nv, propvalFrom) as T;
+ if (valFrom == null) continue;
+ LocalAuditCascade(nav.Value.RefTable, valFrom);
+ }
+ break;
+ }
+ }
+ }
+
+ object LocalAuditJsonElement(Func