|
@@ -7,13 +7,107 @@ using System.Reflection;
|
|
|
using Vit.Linq.ExpressionTree;
|
|
|
using Vit.Linq.ExpressionTree.ComponentModel;
|
|
|
|
|
|
+using Vitorm.Entity;
|
|
|
using Vitorm.Sql.SqlTranslate;
|
|
|
|
|
|
namespace Vitorm.Sql.DataReader
|
|
|
{
|
|
|
public class EntityReader : IDbDataReader
|
|
|
{
|
|
|
- public List<string> sqlFields { get; private set; } = new List<string>();
|
|
|
+
|
|
|
+ public class SqlColumns
|
|
|
+ {
|
|
|
+ List<Column> columns = new();
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// entity field , try get sql column and return sqlColumnIndex
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="sqlTranslator"></param>
|
|
|
+ /// <param name="tableName"></param>
|
|
|
+ /// <param name="columnDescriptor"></param>
|
|
|
+ /// <returns></returns>
|
|
|
+ public int AddSqlColumnAndGetIndex(ISqlTranslateService sqlTranslator, string tableName, IColumnDescriptor columnDescriptor)
|
|
|
+ {
|
|
|
+ var sqlColumnName = sqlTranslator.GetSqlField(tableName, columnDescriptor.columnName);
|
|
|
+
|
|
|
+ var sqlColumnIndex = columns.FirstOrDefault(m => m.sqlColumnName == sqlColumnName)?.sqlColumnIndex ?? -1;
|
|
|
+ if (sqlColumnIndex < 0)
|
|
|
+ {
|
|
|
+ sqlColumnIndex = columns.Count;
|
|
|
+ columns.Add(new Column { tableName = tableName, columnDescriptor = columnDescriptor, sqlColumnName = sqlColumnName, sqlColumnAlias = "c" + sqlColumnIndex, sqlColumnIndex = sqlColumnIndex });
|
|
|
+ }
|
|
|
+ return sqlColumnIndex;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// aggregate column in GroupBy
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="sqlColumnSentence"> for example: Sum([t0].[userId]) , [t0].[userFatherId] </param>
|
|
|
+ /// <returns></returns>
|
|
|
+ public int AddSqlColumnAndGetIndex(string sqlColumnSentence)
|
|
|
+ {
|
|
|
+ var sqlColumnName = sqlColumnSentence;
|
|
|
+
|
|
|
+ var sqlColumnIndex = columns.FirstOrDefault(m => m.sqlColumnName == sqlColumnName)?.sqlColumnIndex ?? -1;
|
|
|
+ if (sqlColumnIndex < 0)
|
|
|
+ {
|
|
|
+ sqlColumnIndex = columns.Count;
|
|
|
+ columns.Add(new Column { sqlColumnName = sqlColumnName, sqlColumnAlias = "c" + sqlColumnIndex, sqlColumnIndex = sqlColumnIndex });
|
|
|
+ }
|
|
|
+ return sqlColumnIndex;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// alias table column ( users.Select(u=> new { u.id } ) )
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="sqlTranslator"></param>
|
|
|
+ /// <param name="member"></param>
|
|
|
+ /// <param name="dbContext"></param>
|
|
|
+ /// <returns></returns>
|
|
|
+ public int AddSqlColumnAndGetIndex(ISqlTranslateService sqlTranslator, ExpressionNode_Member member, DbContext dbContext)
|
|
|
+ {
|
|
|
+ var sqlColumnName = sqlTranslator.GetSqlField(member, dbContext);
|
|
|
+
|
|
|
+ var sqlColumnIndex = columns.FirstOrDefault(m => m.sqlColumnName == sqlColumnName)?.sqlColumnIndex ?? -1;
|
|
|
+ if (sqlColumnIndex < 0)
|
|
|
+ {
|
|
|
+ sqlColumnIndex = columns.Count;
|
|
|
+ columns.Add(new Column { member = member, sqlColumnName = sqlColumnName, sqlColumnAlias = "c" + sqlColumnIndex, sqlColumnIndex = sqlColumnIndex });
|
|
|
+ }
|
|
|
+ return sqlColumnIndex;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public string GetSqlColumns()
|
|
|
+ {
|
|
|
+ var sqlColumns = columns.Select(column => column.sqlColumnName + " as " + column.sqlColumnAlias);
|
|
|
+ return String.Join(", ", sqlColumns);
|
|
|
+ }
|
|
|
+
|
|
|
+ public string GetColumnAliasBySqlColumnName(string sqlColumnName)
|
|
|
+ {
|
|
|
+ return columns.FirstOrDefault(col => col.sqlColumnName == sqlColumnName)?.sqlColumnAlias;
|
|
|
+ }
|
|
|
+
|
|
|
+ class Column
|
|
|
+ {
|
|
|
+ // or table alias
|
|
|
+ public string tableName;
|
|
|
+ public IColumnDescriptor columnDescriptor;
|
|
|
+ public ExpressionNode_Member member;
|
|
|
+
|
|
|
+ public string sqlColumnName;
|
|
|
+ public string sqlColumnAlias;
|
|
|
+
|
|
|
+ public int sqlColumnIndex;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ public SqlColumns sqlColumns = new();
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
|
|
|
protected Type entityType;
|
|
|
protected List<IArgReader> entityArgReaders = new List<IArgReader>();
|
|
@@ -40,13 +134,15 @@ namespace Vitorm.Sql.DataReader
|
|
|
else if (node?.nodeType == NodeType.MethodCall)
|
|
|
{
|
|
|
ExpressionNode_MethodCall methodCall = node;
|
|
|
+
|
|
|
+ // deal with aggregate functions like Sum(id)
|
|
|
if (methodCall.methodCall_typeName == "Enumerable")
|
|
|
{
|
|
|
string argName = null;
|
|
|
|
|
|
- var sqlField = sqlTranslateService.EvalExpression(arg, node);
|
|
|
- var fieldType = methodCall.MethodCall_GetReturnType();
|
|
|
- argName = GetArgument(sqlField, fieldType);
|
|
|
+ var sqlColumnSentence = sqlTranslateService.EvalExpression(arg, node);
|
|
|
+ var columnType = methodCall.MethodCall_GetReturnType();
|
|
|
+ argName = GetArgument(sqlColumnSentence, columnType);
|
|
|
if (argName != null)
|
|
|
{
|
|
|
return (true, ExpressionNode.Member(parameterName: argName, memberName: null));
|
|
@@ -55,22 +151,50 @@ namespace Vitorm.Sql.DataReader
|
|
|
}
|
|
|
return default;
|
|
|
};
|
|
|
- ExpressionNode_New newExp = cloner.Clone(selectedFields);
|
|
|
+ ExpressionNode newSelectedFields = cloner.Clone(selectedFields);
|
|
|
|
|
|
+ // Compile Lambda
|
|
|
+ lambdaCreateEntity = CompileExpression(convertService, entityArgReaders.Select(m => m.argName).ToArray(), newSelectedFields);
|
|
|
|
|
|
- #region Compile Lambda
|
|
|
- var lambdaNode = ExpressionNode.Lambda(entityArgReaders.Select(m => m.argName).ToArray(), (ExpressionNode)newExp);
|
|
|
- //var strNode = Json.Serialize(lambdaNode);
|
|
|
+ return sqlColumns.GetSqlColumns();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ Delegate CompileExpression(ExpressionConvertService convertService, string[] parameterNames, ExpressionNode newExp)
|
|
|
+ {
|
|
|
+ var lambdaNode = ExpressionNode.Lambda(entityArgReaders.Select(m => m.argName).ToArray(), newExp);
|
|
|
+ // var strNode = Json.Serialize(lambdaNode);
|
|
|
|
|
|
var lambdaExp = convertService.ToLambdaExpression(lambdaNode, entityArgReaders.Select(m => m.argType).ToArray());
|
|
|
|
|
|
- lambdaCreateEntity = lambdaExp.Compile();
|
|
|
- #endregion
|
|
|
+ return lambdaExp.Compile();
|
|
|
+ }
|
|
|
+
|
|
|
+ #region CompileExpressionWithCache
|
|
|
+ /*
|
|
|
+ // If it's anonymous CompilerGenerated type, reuse Compiled invoke.
|
|
|
+ // not work because even if it's anonymous CompilerGenerated type, it also can be reused in same method of different lines.
|
|
|
+ static bool cacheEntityCompile = true;
|
|
|
+ static ConcurrentDictionary<Type, Delegate> delegateCache = new();
|
|
|
+ Delegate CompileExpressionWithCache(ExpressionConvertService convertService, string[] parameterNames, ExpressionNode_New newExp)
|
|
|
+ {
|
|
|
+ var type = entityType ?? newExp.New_GetType();
|
|
|
+
|
|
|
+ if (!cacheEntityCompile || type == null)
|
|
|
+ return CompileExpression(convertService, parameterNames, newExp);
|
|
|
+
|
|
|
+ var isCacheable = Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false);
|
|
|
+
|
|
|
+ if (isCacheable && delegateCache.TryGetValue(type, out var lambda)) return lambda;
|
|
|
+
|
|
|
+ lambda = CompileExpression(convertService, parameterNames, newExp);
|
|
|
+
|
|
|
+ if (isCacheable) delegateCache[type] = lambda;
|
|
|
|
|
|
- // sqlFields
|
|
|
- var fields = sqlFields.Select((f, index) => f + " as c" + index);
|
|
|
- return String.Join(", ", fields);
|
|
|
+ return lambda;
|
|
|
}
|
|
|
+ //*/
|
|
|
+ #endregion
|
|
|
|
|
|
|
|
|
public virtual object ReadData(IDataReader reader)
|
|
@@ -117,8 +241,8 @@ namespace Vitorm.Sql.DataReader
|
|
|
if (isValueType)
|
|
|
{
|
|
|
// Value arg
|
|
|
- string sqlFieldName = sqlTranslator.GetSqlField(member, arg.dbContext);
|
|
|
- argReader = new ValueReader(this, argType, argUniqueKey, argName, sqlFieldName);
|
|
|
+ var sqlColumnIndex = sqlColumns.AddSqlColumnAndGetIndex(sqlTranslator, member, arg.dbContext);
|
|
|
+ argReader = new ValueReader(argType, argUniqueKey, argName, sqlColumnIndex);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
@@ -132,9 +256,9 @@ namespace Vitorm.Sql.DataReader
|
|
|
return argReader.argName;
|
|
|
}
|
|
|
|
|
|
- protected string GetArgument(string sqlField, Type fieldType)
|
|
|
+ protected string GetArgument(string sqlColumnSentence, Type columnType)
|
|
|
{
|
|
|
- var argUniqueKey = $"argFunc_{sqlField}";
|
|
|
+ var argUniqueKey = $"argFunc_{sqlColumnSentence}";
|
|
|
|
|
|
IArgReader argReader = entityArgReaders.FirstOrDefault(reader => reader.argUniqueKey == argUniqueKey);
|
|
|
|
|
@@ -142,7 +266,8 @@ namespace Vitorm.Sql.DataReader
|
|
|
{
|
|
|
var argName = "arg_" + entityArgReaders.Count;
|
|
|
|
|
|
- argReader = new ValueReader(this, fieldType, argUniqueKey, argName, sqlField);
|
|
|
+ var sqlColumnIndex = sqlColumns.AddSqlColumnAndGetIndex(sqlColumnSentence);
|
|
|
+ argReader = new ValueReader(columnType, argUniqueKey, argName, sqlColumnIndex);
|
|
|
|
|
|
entityArgReaders.Add(argReader);
|
|
|
}
|