diff --git a/FreeSql.Tests.PerformanceTests/MySqlAdoTest.cs b/FreeSql.Tests.PerformanceTests/MySqlAdoTest.cs index b5091520..f3549082 100644 --- a/FreeSql.Tests.PerformanceTests/MySqlAdoTest.cs +++ b/FreeSql.Tests.PerformanceTests/MySqlAdoTest.cs @@ -20,7 +20,7 @@ namespace FreeSql.Tests.PerformanceTest { time.Restart(); List dplist1 = null; using (var conn = g.mysql.Ado.MasterPool.Get()) { - dplist1 = Dapper.SqlMapper.Query(conn.Value, "select * from song limit 1").ToList(); + dplist1 = Dapper.SqlMapper.Query(conn.Value, "select * from song").ToList(); } time.Stop(); sb.AppendLine($"Elapsed: {time.Elapsed}; Query Entity Counts: {dplist1.Count}; ORM: Dapper"); @@ -44,6 +44,8 @@ namespace FreeSql.Tests.PerformanceTest { + var t31 = g.mysql.Ado.Query("select * from song limit 1"); + time.Restart(); var t3 = g.mysql.Ado.Query("select * from song"); time.Stop(); @@ -130,6 +132,13 @@ namespace FreeSql.Tests.PerformanceTest { var sb = new StringBuilder(); var time = new Stopwatch(); + //var t31 = g.mysql.Select().ToList(); + + time.Restart(); + var t3 = g.mysql.Select().ToList(); + time.Stop(); + sb.AppendLine($"Elapsed: {time.Elapsed}; ToList Entity Counts: {t3.Count}; ORM: FreeSql*"); + time.Restart(); List dplist1 = null; using (var conn = g.mysql.Ado.MasterPool.Get()) { @@ -137,12 +146,6 @@ namespace FreeSql.Tests.PerformanceTest { } time.Stop(); sb.AppendLine($"Elapsed: {time.Elapsed}; Query Entity Counts: {dplist1.Count}; ORM: Dapper"); - - - time.Restart(); - var t3 = g.mysql.Select().ToList(); - time.Stop(); - sb.AppendLine($"Elapsed: {time.Elapsed}; ToList Entity Counts: {t3.Count}; ORM: FreeSql*"); } [Fact] @@ -150,6 +153,19 @@ namespace FreeSql.Tests.PerformanceTest { var sb = new StringBuilder(); var time = new Stopwatch(); + time.Restart(); + var t3Count = 0; + var p3 = Parallel.For(1, 50, b => { + List t3 = new List(); + for (var a = 0; a < 1000; a++) { + t3.AddRange(g.mysql.Select().Limit(50).ToList()); + } + Interlocked.Add(ref t3Count, t3.Count); + }); + while (p3.IsCompleted == false) ; + time.Stop(); + sb.AppendLine($"Elapsed: {time.Elapsed}; ToList Entity Counts: {t3Count}; ORM: FreeSql*"); + time.Restart(); var dplist1Count = 0; var p1 = Parallel.For(1, 50, b => { @@ -159,25 +175,11 @@ namespace FreeSql.Tests.PerformanceTest { dplist1.AddRange(Dapper.SqlMapper.Query(conn.Value, "select * from song limit 50").ToList()); } } - Interlocked.Exchange(ref dplist1Count, dplist1.Count); + Interlocked.Add(ref dplist1Count, dplist1.Count); }); while (p1.IsCompleted == false) ; time.Stop(); sb.AppendLine($"Elapsed: {time.Elapsed}; Query Entity Counts: {dplist1Count}; ORM: Dapper"); - - - time.Restart(); - var t3Count = 0; - var p3 = Parallel.For(1, 50, b => { - List t3 = new List(); - for (var a = 0; a < 1000; a++) { - t3.AddRange(g.mysql.Select().Limit(50).ToList()); - } - Interlocked.Exchange(ref t3Count, t3.Count); - }); - while (p3.IsCompleted == false) ; - time.Stop(); - sb.AppendLine($"Elapsed: {time.Elapsed}; ToList Entity Counts: {t3Count}; ORM: FreeSql*"); } [Table(Name = "song")] diff --git a/FreeSql/FreeSql.csproj b/FreeSql/FreeSql.csproj index b31fbb27..8ab3ad7b 100644 --- a/FreeSql/FreeSql.csproj +++ b/FreeSql/FreeSql.csproj @@ -2,7 +2,7 @@ netstandard2.0 - 0.0.6 + 0.0.7 true YeXiangQin 打造 .NETCore 最方便的 ORM,DbFirst 与 CodeFirst 混合使用,提供从实体同步数据库,或者从数据库生成实体代码,支持 MySql/SqlServer/PostgreSQL/Oracle/Sqlite 数据库。 diff --git a/FreeSql/Internal/CommonProvider/AdoProvider/AdoProvider.cs b/FreeSql/Internal/CommonProvider/AdoProvider/AdoProvider.cs index 6ada413a..67d666d7 100644 --- a/FreeSql/Internal/CommonProvider/AdoProvider/AdoProvider.cs +++ b/FreeSql/Internal/CommonProvider/AdoProvider/AdoProvider.cs @@ -66,13 +66,15 @@ namespace FreeSql.Internal.CommonProvider { public List Query(CommandType cmdType, string cmdText, params DbParameter[] cmdParms) { var names = new Dictionary(StringComparer.CurrentCultureIgnoreCase); var ret = new List(); + var type = typeof(T); + var defaultValue = default(T); ExecuteReader(dr => { if (names.Any() == false) for (var a = 0; a < dr.FieldCount; a++) names.Add(dr.GetName(a), a); - object[] values = new object[dr.FieldCount]; + object[] values = new object[names.Count]; dr.GetValues(values); - var read = Utils.ExecuteArrayRowReadClassOrTuple(typeof(T), names, values, 0); - ret.Add(read.value == null ? default(T) : (T)read.value); + var read = Utils.ExecuteArrayRowReadClassOrTuple(type, names, values, 0); + ret.Add(read.Value == null ? defaultValue : (T)read.Value); }, cmdType, cmdText, cmdParms); return ret; } diff --git a/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderAsync.cs b/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderAsync.cs index 965ff392..40faba24 100644 --- a/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderAsync.cs +++ b/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderAsync.cs @@ -23,7 +23,7 @@ namespace FreeSql.Internal.CommonProvider { var ret = new List(); foreach (var row in ds) { var read = Utils.ExecuteArrayRowReadClassOrTuple(typeof(T), names, row); - ret.Add(read.value == null ? default(T) : (T) read.value); + ret.Add(read.Value == null ? default(T) : (T) read.Value); } return ret; } diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs b/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs index 0d3f4d99..35ff52b1 100644 --- a/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs +++ b/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs @@ -150,7 +150,7 @@ namespace FreeSql.Internal.CommonProvider { var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql, _params.ToArray()); foreach (var dr in ds) { var read = Utils.ExecuteArrayRowReadClassOrTuple(type, null, dr); - ret.Add(read.value == null ? default(TTuple) : (TTuple)read.value); + ret.Add(read.Value == null ? default(TTuple) : (TTuple)read.Value); } return ret; }); @@ -165,7 +165,7 @@ namespace FreeSql.Internal.CommonProvider { var ds = await _orm.Ado.ExecuteArrayAsync(CommandType.Text, sql, _params.ToArray()); foreach (var dr in ds) { var read = Utils.ExecuteArrayRowReadClassOrTuple(type, null, dr); - ret.Add(read.value == null ? default(TTuple) : (TTuple)read.value); + ret.Add(read.Value == null ? default(TTuple) : (TTuple)read.Value); } return ret; }); @@ -221,16 +221,12 @@ namespace FreeSql.Internal.CommonProvider { } protected (ReadAnonymousTypeInfo map, string field) GetAllField() { var type = typeof(T1); - if (Utils._dicClassConstructor.TryGetValue(type, out var classInfo) == false) { - classInfo = new Utils._dicClassConstructorInfo { Constructor = type.GetConstructor(new Type[0]), Properties = type.GetProperties() }; - Utils._dicClassConstructor.TryAdd(type, classInfo); - } - var map = new ReadAnonymousTypeInfo { Consturctor = classInfo.Constructor, ConsturctorType = ReadAnonymousTypeInfoConsturctorType.Properties }; + var map = new ReadAnonymousTypeInfo { Consturctor = type.GetConstructor(new Type[0]), ConsturctorType = ReadAnonymousTypeInfoConsturctorType.Properties }; var field = new StringBuilder(); var dicfield = new Dictionary(); var tb = _tables.First(); var index = 0; - var ps = classInfo.Properties; + var ps = type.GetProperties(); foreach (var p in ps) { var child = new ReadAnonymousTypeInfo { Property = p, CsName = p.Name }; if (tb.Table.ColumnsByCs.TryGetValue(p.Name, out var col)) { //普通字段 diff --git a/FreeSql/Internal/UtilsExpressionTree.cs b/FreeSql/Internal/UtilsExpressionTree.cs new file mode 100644 index 00000000..44a3fa7a --- /dev/null +++ b/FreeSql/Internal/UtilsExpressionTree.cs @@ -0,0 +1,463 @@ +using FreeSql.DataAnnotations; +using FreeSql.Internal.Model; +using Newtonsoft.Json.Linq; +using Npgsql.LegacyPostgis; +using NpgsqlTypes; +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Linq.Expressions; +using System.Net; +using System.Net.NetworkInformation; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Threading; + +namespace FreeSql.Internal { + class Utils { + + static ConcurrentDictionary _cacheGetTableByEntity = new ConcurrentDictionary(); + internal static TableInfo GetTableByEntity(Type entity, CommonUtils common) { + if (entity.FullName.StartsWith("<>f__AnonymousType")) return null; + if (_cacheGetTableByEntity.TryGetValue($"{common.DbName}-{entity.FullName}", out var trytb)) return trytb; //区分数据库类型缓存 + if (common.CodeFirst.GetDbInfo(entity) != null) return null; + + var tbattr = entity.GetCustomAttributes(typeof(TableAttribute), false).LastOrDefault() as TableAttribute; + trytb = new TableInfo(); + trytb.Type = entity; + trytb.Properties = entity.GetProperties().ToDictionary(a => a.Name, a => a, StringComparer.CurrentCultureIgnoreCase); + trytb.CsName = entity.Name; + trytb.DbName = (tbattr?.Name ?? entity.Name); + trytb.DbOldName = tbattr?.OldName; + if (common.CodeFirst.IsSyncStructureToLower) { + trytb.DbName = trytb.DbName.ToLower(); + trytb.DbOldName = trytb.DbOldName?.ToLower(); + } + trytb.SelectFilter = tbattr?.SelectFilter; + foreach (var p in trytb.Properties.Values) { + var tp = common.CodeFirst.GetDbInfo(p.PropertyType); + //if (tp == null) continue; + var colattr = p.GetCustomAttributes(typeof(ColumnAttribute), false).LastOrDefault() as ColumnAttribute; + if (tp == null && colattr == null) continue; + if (colattr == null) + colattr = new ColumnAttribute { + Name = p.Name, + DbType = tp.Value.dbtypeFull, + IsIdentity = false, + IsNullable = tp.Value.isnullable ?? true, + IsPrimary = false, + }; + if (string.IsNullOrEmpty(colattr.DbType)) colattr.DbType = tp?.dbtypeFull ?? "varchar(255)"; + colattr.DbType = colattr.DbType.ToUpper(); + + if (tp != null && tp.Value.isnullable == null) colattr.IsNullable = tp.Value.dbtypeFull.Contains("NOT NULL") == false; + if (colattr.DbType?.Contains("NOT NULL") == true) colattr.IsNullable = false; + if (string.IsNullOrEmpty(colattr.Name)) colattr.Name = p.Name; + if (common.CodeFirst.IsSyncStructureToLower) colattr.Name = colattr.Name.ToLower(); + + if ((colattr.IsNullable == false || colattr.IsIdentity || colattr.IsPrimary) && colattr.DbType.Contains("NOT NULL") == false) { + colattr.IsNullable = false; + colattr.DbType += " NOT NULL"; + } + if (colattr.IsNullable == true && colattr.DbType.Contains("NOT NULL")) colattr.DbType = colattr.DbType.Replace("NOT NULL", ""); + colattr.DbType = Regex.Replace(colattr.DbType, @"\([^\)]+\)", m => { + var tmpLt = Regex.Replace(m.Groups[0].Value, @"\s", ""); + if (tmpLt.Contains("CHAR")) tmpLt = tmpLt.Replace("CHAR", " CHAR"); + if (tmpLt.Contains("BYTE")) tmpLt = tmpLt.Replace("BYTE", " BYTE"); + return tmpLt; + }); + colattr.DbDefautValue = trytb.Properties[p.Name].GetValue(Activator.CreateInstance(trytb.Type)); + if (colattr.DbDefautValue == null) colattr.DbDefautValue = tp?.defaultValue; + if (colattr.IsNullable == false && colattr.DbDefautValue == null) { + var consturctorType = p.PropertyType.GenericTypeArguments.FirstOrDefault() ?? p.PropertyType; + colattr.DbDefautValue = Activator.CreateInstance(consturctorType); + } + + var col = new ColumnInfo { + Table = trytb, + CsName = p.Name, + CsType = p.PropertyType, + Attribute = colattr + }; + trytb.Columns.Add(colattr.Name, col); + trytb.ColumnsByCs.Add(p.Name, col); + } + trytb.Primarys = trytb.Columns.Values.Where(a => a.Attribute.IsPrimary).ToArray(); + if (trytb.Primarys.Any() == false) { + trytb.Primarys = trytb.Columns.Values.Where(a => a.Attribute.IsIdentity).ToArray(); + foreach(var col in trytb.Primarys) + col.Attribute.IsPrimary = true; + } + _cacheGetTableByEntity.TryAdd(entity.FullName, trytb); + return trytb; + } + + internal static T[] GetDbParamtersByObject(string sql, object obj, string paramPrefix, Func constructorParamter) { + if (string.IsNullOrEmpty(sql) || obj == null) return new T[0]; + var ttype = typeof(T); + var type = obj.GetType(); + if (type == ttype) return new[] { (T)Convert.ChangeType(obj, type) }; + var ret = new List(); + var ps = type.GetProperties(); + foreach (var p in ps) { + if (sql.IndexOf($"{paramPrefix}{p.Name}", StringComparison.CurrentCultureIgnoreCase) == -1) continue; + var pvalue = p.GetValue(obj); + if (p.PropertyType == ttype) ret.Add((T)Convert.ChangeType(pvalue, ttype)); + else ret.Add(constructorParamter(p.Name, p.PropertyType, pvalue)); + } + return ret.ToArray(); + } + + static Dictionary dicExecuteArrayRowReadClassOrTuple = new Dictionary { + [typeof(bool)] = true, + [typeof(sbyte)] = true, + [typeof(short)] = true, + [typeof(int)] = true, + [typeof(long)] = true, + [typeof(byte)] = true, + [typeof(ushort)] = true, + [typeof(uint)] = true, + [typeof(ulong)] = true, + [typeof(double)] = true, + [typeof(float)] = true, + [typeof(decimal)] = true, + [typeof(TimeSpan)] = true, + [typeof(DateTime)] = true, + [typeof(DateTimeOffset)] = true, + [typeof(byte[])] = true, + [typeof(string)] = true, + [typeof(Guid)] = true, + [typeof(MygisPoint)] = true, + [typeof(MygisLineString)] = true, + [typeof(MygisPolygon)] = true, + [typeof(MygisMultiPoint)] = true, + [typeof(MygisMultiLineString)] = true, + [typeof(MygisMultiPolygon)] = true, + [typeof(BitArray)] = true, + [typeof(NpgsqlPoint)] = true, + [typeof(NpgsqlLine)] = true, + [typeof(NpgsqlLSeg)] = true, + [typeof(NpgsqlBox)] = true, + [typeof(NpgsqlPath)] = true, + [typeof(NpgsqlPolygon)] = true, + [typeof(NpgsqlCircle)] = true, + [typeof((IPAddress Address, int Subnet))] = true, + [typeof(IPAddress)] = true, + [typeof(PhysicalAddress)] = true, + [typeof(NpgsqlRange)] = true, + [typeof(NpgsqlRange)] = true, + [typeof(NpgsqlRange)] = true, + [typeof(NpgsqlRange)] = true, + [typeof(PostgisPoint)] = true, + [typeof(PostgisLineString)] = true, + [typeof(PostgisPolygon)] = true, + [typeof(PostgisMultiPoint)] = true, + [typeof(PostgisMultiLineString)] = true, + [typeof(PostgisMultiPolygon)] = true, + [typeof(PostgisGeometry)] = true, + [typeof(PostgisGeometryCollection)] = true, + [typeof(Dictionary)] = true, + [typeof(JToken)] = true, + [typeof(JObject)] = true, + [typeof(JArray)] = true, + }; + static ConcurrentDictionary, object[], int, RowInfo>> _dicExecuteArrayRowReadClassOrTuple = new ConcurrentDictionary, object[], int, RowInfo>>(); + internal class RowInfo { + public object Value { get; set; } + public int DataIndex { get; set; } + public RowInfo(object value, int dataIndex) { + this.Value = value; + this.DataIndex = dataIndex; + } + public static ConstructorInfo Constructor = typeof(RowInfo).GetConstructor(new[] { typeof(object), typeof(int) }); + public static PropertyInfo PropertyValue = typeof(RowInfo).GetProperty("Value"); + public static PropertyInfo PropertyDataIndex = typeof(RowInfo).GetProperty("DataIndex"); + } + internal static RowInfo ExecuteArrayRowReadClassOrTuple(Type type, Dictionary names, object[] row, int dataIndex = 0) { + var func = _dicExecuteArrayRowReadClassOrTuple.GetOrAdd(type, s => { + var returnTarget = Expression.Label(typeof(RowInfo)); + var typeExp = Expression.Parameter(typeof(Type), "type"); + var namesExp = Expression.Parameter(typeof(Dictionary), "names"); + var rowExp = Expression.Parameter(typeof(object[]), "row"); + var dataIndexExp = Expression.Parameter(typeof(int), "dataIndex"); + + if (type.IsArray) return Expression.Lambda, object[], int, RowInfo>>( + Expression.New(RowInfo.Constructor, + Expression.Call(MethodGetDataReaderValue, new Expression[] { typeExp, Expression.ArrayAccess(rowExp, dataIndexExp) }), + Expression.Add(dataIndexExp, Expression.Constant(1)) + ), new[] { typeExp, namesExp, rowExp, dataIndexExp }).Compile(); + + var typeGeneric = type; + if (typeGeneric.FullName.StartsWith("System.Nullable`1[")) typeGeneric = type.GenericTypeArguments.First(); + if (typeGeneric.IsEnum || + dicExecuteArrayRowReadClassOrTuple.ContainsKey(typeGeneric)) + return Expression.Lambda, object[], int, RowInfo>>( + Expression.New(RowInfo.Constructor, + Expression.Call(MethodGetDataReaderValue, new Expression[] { typeExp, Expression.ArrayAccess(rowExp, dataIndexExp) }), + Expression.Add(dataIndexExp, Expression.Constant(1)) + ), new[] { typeExp, namesExp, rowExp, dataIndexExp }).Compile(); + + if (type.Namespace == "System" && (type.FullName == "System.String" || type.IsValueType)) { //值类型,或者元组 + bool isTuple = type.Name.StartsWith("ValueTuple`"); + if (isTuple) { + var ret2Exp = Expression.Variable(type, "ret"); + var read2Exp = Expression.Variable(typeof(RowInfo), "read"); + var read2ExpValue = Expression.MakeMemberAccess(read2Exp, RowInfo.PropertyValue); + var read2ExpDataIndex = Expression.MakeMemberAccess(read2Exp, RowInfo.PropertyDataIndex); + var block2Exp = new List(); + + var fields = type.GetFields(); + foreach (var field in fields) { + block2Exp.AddRange(new Expression[] { + //Expression.TryCatch(Expression.Block( + // typeof(void), + Expression.Assign(read2Exp, Expression.Call(MethodExecuteArrayRowReadClassOrTuple, new Expression[] { Expression.Constant(field.FieldType), namesExp, rowExp, dataIndexExp })), + Expression.IfThen(Expression.GreaterThan(read2ExpDataIndex, dataIndexExp), + Expression.Assign(dataIndexExp, read2ExpDataIndex)), + Expression.Assign(Expression.MakeMemberAccess(ret2Exp, field), Expression.Convert(read2ExpValue, field.FieldType)) + //), + //Expression.Catch(typeof(Exception), Expression.Block( + // Expression.IfThen(Expression.Equal(read2ExpDataIndex, Expression.Constant(0)), Expression.Throw(Expression.Constant(new Exception(field.Name + "," + 0)))), + // Expression.IfThen(Expression.Equal(read2ExpDataIndex, Expression.Constant(1)), Expression.Throw(Expression.Constant(new Exception(field.Name + "," + 1)))), + // Expression.IfThen(Expression.Equal(read2ExpDataIndex, Expression.Constant(2)), Expression.Throw(Expression.Constant(new Exception(field.Name + "," + 2)))), + // Expression.IfThen(Expression.Equal(read2ExpDataIndex, Expression.Constant(3)), Expression.Throw(Expression.Constant(new Exception(field.Name + "," + 3)))), + // Expression.IfThen(Expression.Equal(read2ExpDataIndex, Expression.Constant(4)), Expression.Throw(Expression.Constant(new Exception(field.Name + "," + 4)))) + // ) + //)) + }); + } + block2Exp.AddRange(new Expression[] { + Expression.Return(returnTarget, Expression.New(RowInfo.Constructor, Expression.Convert(ret2Exp, typeof(object)), dataIndexExp)), + Expression.Label(returnTarget, Expression.Default(typeof(RowInfo))) + }); + return Expression.Lambda, object[], int, RowInfo>>( + Expression.Block(new[] { ret2Exp, read2Exp }, block2Exp), new[] { typeExp, namesExp, rowExp, dataIndexExp }).Compile(); + } + var rowLenExp = Expression.ArrayLength(rowExp); + return Expression.Lambda, object[], int, RowInfo>>( + Expression.Block( + Expression.IfThen( + Expression.LessThan(dataIndexExp, rowLenExp), + Expression.Return(returnTarget, Expression.New(RowInfo.Constructor, + Expression.Call(MethodGetDataReaderValue, new Expression[] { typeExp, Expression.ArrayAccess(rowExp, dataIndexExp) }), + Expression.Add(dataIndexExp, Expression.Constant(1)))) + ), + Expression.Label(returnTarget, Expression.Default(typeof(RowInfo))) + ), new[] { typeExp, namesExp, rowExp, dataIndexExp }).Compile(); + } + + if (type == typeof(object) && names != null) { + Func, object[], int, RowInfo> dynamicFunc = (type2, names2, row2, dataindex2) => { + dynamic expando = new System.Dynamic.ExpandoObject(); //动态类型字段 可读可写 + var expandodic = (IDictionary)expando; + foreach (var name in names2) + expandodic.Add(name.Key, row2[name.Value]); + return new RowInfo(expando, names2.Count); + }; + return dynamicFunc;// Expression.Lambda, object[], int, RowInfo>>(null); + } + + //类注入属性 + var retExp = Expression.Variable(type, "ret"); + var readExp = Expression.Variable(typeof(RowInfo), "read"); + var readExpValue = Expression.MakeMemberAccess(readExp, RowInfo.PropertyValue); + var readExpDataIndex = Expression.MakeMemberAccess(readExp, RowInfo.PropertyDataIndex); + var tryidxExp = Expression.Variable(typeof(int), "tryidx"); + var blockExp = new List(); + blockExp.Add(Expression.Assign(retExp, Expression.New(type.GetConstructor(new Type[0])))); + + var props = type.GetProperties(); + foreach (var prop in props) { + var propGetSetMethod = prop.GetSetMethod(); + blockExp.AddRange(new Expression[] { + Expression.Assign(tryidxExp, dataIndexExp), + Expression.IfThen(Expression.Not(Expression.And( + Expression.NotEqual(namesExp, Expression.Constant(null)), + Expression.Not(Expression.Call(namesExp, namesExp.Type.GetMethod("TryGetValue"), Expression.Constant(prop.Name), tryidxExp)))), + Expression.Block( + //Expression.Assign(tryidxExp, Expression.Call(namesExp, namesExp Expression.Constant(prop.Name))), + Expression.Assign(readExp, Expression.Call(MethodExecuteArrayRowReadClassOrTuple, new Expression[] { Expression.Constant(prop.PropertyType), namesExp, rowExp, tryidxExp })), + Expression.IfThen(Expression.GreaterThan(readExpDataIndex, dataIndexExp), + Expression.Assign(dataIndexExp, readExpDataIndex)), + Expression.IfThen(Expression.NotEqual(readExpValue, Expression.Constant(null)), + Expression.Call(retExp, propGetSetMethod, Expression.Convert(readExpValue, prop.PropertyType))) + ) + ) + }); + } + blockExp.AddRange(new Expression[] { + Expression.Return(returnTarget, Expression.New(RowInfo.Constructor, retExp, dataIndexExp)), + Expression.Label(returnTarget, Expression.Default(typeof(RowInfo))) + }); + return Expression.Lambda, object[], int, RowInfo>>( + Expression.Block(new[] { retExp, readExp, tryidxExp }, blockExp), new[] { typeExp, namesExp, rowExp, dataIndexExp }).Compile(); + }); + + return func(type, names, row, dataIndex); + } + + static MethodInfo MethodExecuteArrayRowReadClassOrTuple = typeof(Utils).GetMethod("ExecuteArrayRowReadClassOrTuple", BindingFlags.Static | BindingFlags.NonPublic); + static MethodInfo MethodGetDataReaderValue = typeof(Utils).GetMethod("GetDataReaderValue", BindingFlags.Static | BindingFlags.NonPublic); + + static ConcurrentDictionary> _dicFillPropertyValue = new ConcurrentDictionary>(); + internal static void FillPropertyValue(object info, string memberAccessPath, object value) { + var typeObj = info.GetType(); + var typeValue = value.GetType(); + var key = "FillPropertyValue_" + typeObj.FullName + "_" + typeValue.FullName; + var act = _dicFillPropertyValue.GetOrAdd($"{key}.{memberAccessPath}", s => { + var parmInfo = Expression.Parameter(typeof(object), "info"); + var parmValue = Expression.Parameter(typeof(object), "value"); + Expression exp = Expression.Convert(parmInfo, typeObj); + foreach (var pro in memberAccessPath.Split('.')) + exp = Expression.PropertyOrField(exp, pro) ?? throw new Exception(string.Concat(exp.Type.FullName, " 没有定义属性 ", pro)); + + var value2 = Expression.Call(MethodGetDataReaderValue, Expression.Constant(exp.Type), parmValue); + var value3 = Expression.Convert(parmValue, typeValue); + exp = Expression.Assign(exp, value3); + return Expression.Lambda>(exp, parmInfo, parmValue).Compile(); + }); + act(info, value); + } + + static ConcurrentDictionary> _dicGetDataReaderValue = new ConcurrentDictionary>(); + internal static object GetDataReaderValue(Type type, object value) { + if (value == null || value == DBNull.Value) return null; + + var func = _dicGetDataReaderValue.GetOrAdd(type, k => { + var returnTarget = Expression.Label(typeof(object)); + var parmExp = Expression.Parameter(typeof(object), "value"); + + if (type.FullName == "System.Byte[]") return Expression.Lambda>(parmExp, parmExp).Compile(); + + if (type.IsArray) { + var elementType = type.GetElementType(); + var valueArr = value as Array; + + if (elementType == valueArr.GetType().GetElementType()) return Expression.Lambda>(parmExp, parmExp).Compile(); + + var arr = Expression.Variable(type, "arr"); + var arrlen = Expression.Variable(typeof(int), "arrlen"); + var x = Expression.Variable(typeof(int), "x"); + var label = Expression.Label(typeof(int)); + var ret = Expression.NewArrayBounds(elementType, arrlen); + return Expression.Lambda>( + Expression.Block( + new[] { arr, arrlen, x }, + Expression.Assign(arr, Expression.Convert(parmExp, type)), + Expression.Assign(arrlen, Expression.ArrayLength(arr)), + Expression.Assign(x, Expression.Constant(0)), + ret, + Expression.Loop( + Expression.IfThenElse( + Expression.LessThan(x, arrlen), + Expression.Block( + Expression.Assign( + Expression.ArrayAccess(ret, x), + Expression.Call( + MethodGetDataReaderValue, + Expression.Constant(elementType, typeof(Type)), + Expression.ArrayAccess(arr, x) + ) + ), + Expression.PostIncrementAssign(x) + ), + Expression.Break(label, x) + ), + label + ) + ), parmExp).Compile(); + } + + if (type.FullName.StartsWith("System.Nullable`1[")) type = type.GenericTypeArguments.First(); + if (type.IsEnum) return Expression.Lambda>( + Expression.Call( + typeof(Enum).GetMethod("Parse", new[] { typeof(Type), typeof(string), typeof(bool) }), + Expression.Constant(type, typeof(Type)), + Expression.Convert(parmExp, typeof(string)), + Expression.Constant(true, typeof(bool)) + ) , parmExp).Compile(); + + switch (type.FullName) { + case "System.Guid": + if (value.GetType() != type) return Expression.Lambda>( + Expression.Block( + Expression.TryCatch( + Expression.Return(returnTarget, Expression.Call(typeof(Guid).GetMethod("Parse"), Expression.Convert(parmExp, typeof(string)))), + Expression.Catch(typeof(Exception), + Expression.Return(returnTarget, Expression.Constant(Guid.Empty))) + ), + Expression.Label(returnTarget, Expression.Default(type)) + ), parmExp).Compile(); + + return Expression.Lambda>(parmExp, parmExp).Compile(); + + case "MygisPoint": return Expression.Lambda>( + Expression.TypeAs( + Expression.Call(typeof(MygisPoint).GetMethod("Parse"), Expression.Convert(parmExp, typeof(string))), + typeof(MygisPoint) + ), parmExp).Compile(); + case "MygisLineString": return Expression.Lambda>( + Expression.TypeAs( + Expression.Call(typeof(MygisLineString).GetMethod("Parse"), Expression.Convert(parmExp, typeof(string))), + typeof(MygisLineString) + ), parmExp).Compile(); + case "MygisPolygon": return Expression.Lambda>( + Expression.TypeAs( + Expression.Call(typeof(MygisPolygon).GetMethod("Parse"), Expression.Convert(parmExp, typeof(string))), + typeof(MygisPolygon) + ), parmExp).Compile(); + case "MygisMultiPoint": return Expression.Lambda>( + Expression.TypeAs( + Expression.Call(typeof(MygisMultiPoint).GetMethod("Parse"), Expression.Convert(parmExp, typeof(string))), + typeof(MygisMultiPoint) + ), parmExp).Compile(); + case "MygisMultiLineString": return Expression.Lambda>( + Expression.TypeAs( + Expression.Call(typeof(MygisMultiLineString).GetMethod("Parse"), Expression.Convert(parmExp, typeof(string))), + typeof(MygisMultiLineString) + ), parmExp).Compile(); + case "MygisMultiPolygon": return Expression.Lambda>( + Expression.TypeAs( + Expression.Call(typeof(MygisMultiPolygon).GetMethod("Parse"), Expression.Convert(parmExp, typeof(string))), + typeof(MygisMultiPolygon) + ), parmExp).Compile(); + case "Newtonsoft.Json.Linq.JToken": return Expression.Lambda>( + Expression.TypeAs( + Expression.Call(typeof(JToken).GetMethod("Parse", new[] { typeof(string) }), Expression.Convert(parmExp, typeof(string))), + typeof(JToken) + ), parmExp).Compile(); + case "Newtonsoft.Json.Linq.JObject": return Expression.Lambda>( + Expression.TypeAs( + Expression.Call(typeof(JObject).GetMethod("Parse", new[] { typeof(string) }), Expression.Convert(parmExp, typeof(string))), + typeof(JObject) + ), parmExp).Compile(); + case "Newtonsoft.Json.Linq.JArray": return Expression.Lambda>( + Expression.TypeAs( + Expression.Call(typeof(JArray).GetMethod("Parse", new[] { typeof(string) }), Expression.Convert(parmExp, typeof(string))), + typeof(JArray) + ), parmExp).Compile(); + case "Npgsql.LegacyPostgis.PostgisGeometry": return Expression.Lambda>(parmExp, parmExp).Compile(); + } + if (type != value.GetType()) { + if (type.FullName == "System.TimeSpan") return Expression.Lambda>( + Expression.Call( + typeof(TimeSpan).GetMethod("FromSeconds"), + Expression.Call(typeof(double).GetMethod("Parse", new[] { typeof(string) }), Expression.Convert(parmExp, typeof(string))) + ), parmExp).Compile(); + return Expression.Lambda>( + Expression.Call(typeof(Convert).GetMethod("ChangeType", new[] { typeof(object), typeof(Type) }), parmExp, Expression.Constant(type, typeof(Type))) + , parmExp).Compile(); + } + return Expression.Lambda>(parmExp, parmExp).Compile(); + }); + return func(value); + } + internal static string GetCsName(string name) { + name = Regex.Replace(name.TrimStart('@'), @"[^\w]", "_"); + return char.IsLetter(name, 0) ? name : string.Concat("_", name); + } + } +} \ No newline at end of file diff --git a/FreeSql/Internal/Utils.cs b/FreeSql/Internal/UtilsReflection.cs similarity index 81% rename from FreeSql/Internal/Utils.cs rename to FreeSql/Internal/UtilsReflection.cs index dc1df814..0018e85e 100644 --- a/FreeSql/Internal/Utils.cs +++ b/FreeSql/Internal/UtilsReflection.cs @@ -16,7 +16,7 @@ using System.Text.RegularExpressions; using System.Threading; namespace FreeSql.Internal { - class Utils { + class UtilsReflection { static ConcurrentDictionary _cacheGetTableByEntity = new ConcurrentDictionary(); internal static TableInfo GetTableByEntity(Type entity, CommonUtils common) { @@ -110,59 +110,60 @@ namespace FreeSql.Internal { return ret.ToArray(); } - static Dictionary dicExecuteArrayRowReadClassOrTuple = new Dictionary { - { typeof(bool).FullName, true }, - { typeof(sbyte).FullName, true }, - { typeof(short).FullName, true }, - { typeof(int).FullName, true }, - { typeof(long).FullName, true }, - { typeof(byte).FullName, true }, - { typeof(ushort).FullName, true }, - { typeof(uint).FullName, true }, - { typeof(ulong).FullName, true }, - { typeof(double).FullName, true }, - { typeof(float).FullName, true }, - { typeof(decimal).FullName, true }, - { typeof(TimeSpan).FullName, true }, - { typeof(DateTime).FullName, true }, - { typeof(DateTimeOffset).FullName, true }, - { typeof(byte[]).FullName, true }, - { typeof(string).FullName, true }, - { typeof(Guid).FullName, true }, - { typeof(MygisPoint).FullName, true }, - { typeof(MygisLineString).FullName, true }, - { typeof(MygisPolygon).FullName, true }, - { typeof(MygisMultiPoint).FullName, true }, - { typeof(MygisMultiLineString).FullName, true }, - { typeof(MygisMultiPolygon).FullName, true }, - { typeof(BitArray).FullName, true }, - { typeof(NpgsqlPoint).FullName, true }, - { typeof(NpgsqlLine).FullName, true }, - { typeof(NpgsqlLSeg).FullName, true }, - { typeof(NpgsqlBox).FullName, true }, - { typeof(NpgsqlPath).FullName, true }, - { typeof(NpgsqlPolygon).FullName, true }, - { typeof(NpgsqlCircle).FullName, true }, - { typeof((IPAddress Address, int Subnet)).FullName, true }, - { typeof(IPAddress).FullName, true }, - { typeof(PhysicalAddress).FullName, true }, - { typeof(NpgsqlRange).FullName, true }, - { typeof(NpgsqlRange).FullName, true }, - { typeof(NpgsqlRange).FullName, true }, - { typeof(NpgsqlRange).FullName, true }, - { typeof(PostgisPoint).FullName, true }, - { typeof(PostgisLineString).FullName, true }, - { typeof(PostgisPolygon).FullName, true }, - { typeof(PostgisMultiPoint).FullName, true }, - { typeof(PostgisMultiLineString).FullName, true }, - { typeof(PostgisMultiPolygon).FullName, true }, - { typeof(PostgisGeometry).FullName, true }, - { typeof(PostgisGeometryCollection).FullName, true }, - { typeof(Dictionary).FullName, true }, - { typeof(JToken).FullName, true }, - { typeof(JObject).FullName, true }, - { typeof(JArray).FullName, true }, + static Dictionary dicExecuteArrayRowReadClassOrTuple = new Dictionary { + [typeof(bool)] = true, + [typeof(sbyte)] = true, + [typeof(short)] = true, + [typeof(int)] = true, + [typeof(long)] = true, + [typeof(byte)] = true, + [typeof(ushort)] = true, + [typeof(uint)] = true, + [typeof(ulong)] = true, + [typeof(double)] = true, + [typeof(float)] = true, + [typeof(decimal)] = true, + [typeof(TimeSpan)] = true, + [typeof(DateTime)] = true, + [typeof(DateTimeOffset)] = true, + [typeof(byte[])] = true, + [typeof(string)] = true, + [typeof(Guid)] = true, + [typeof(MygisPoint)] = true, + [typeof(MygisLineString)] = true, + [typeof(MygisPolygon)] = true, + [typeof(MygisMultiPoint)] = true, + [typeof(MygisMultiLineString)] = true, + [typeof(MygisMultiPolygon)] = true, + [typeof(BitArray)] = true, + [typeof(NpgsqlPoint)] = true, + [typeof(NpgsqlLine)] = true, + [typeof(NpgsqlLSeg)] = true, + [typeof(NpgsqlBox)] = true, + [typeof(NpgsqlPath)] = true, + [typeof(NpgsqlPolygon)] = true, + [typeof(NpgsqlCircle)] = true, + [typeof((IPAddress Address, int Subnet))] = true, + [typeof(IPAddress)] = true, + [typeof(PhysicalAddress)] = true, + [typeof(NpgsqlRange)] = true, + [typeof(NpgsqlRange)] = true, + [typeof(NpgsqlRange)] = true, + [typeof(NpgsqlRange)] = true, + [typeof(PostgisPoint)] = true, + [typeof(PostgisLineString)] = true, + [typeof(PostgisPolygon)] = true, + [typeof(PostgisMultiPoint)] = true, + [typeof(PostgisMultiLineString)] = true, + [typeof(PostgisMultiPolygon)] = true, + [typeof(PostgisGeometry)] = true, + [typeof(PostgisGeometryCollection)] = true, + [typeof(Dictionary)] = true, + [typeof(JToken)] = true, + [typeof(JObject)] = true, + [typeof(JArray)] = true, }; + internal static ConcurrentDictionary _dicClassConstructor = new ConcurrentDictionary(); internal static ConcurrentDictionary _dicTupleConstructor = new ConcurrentDictionary(); internal class _dicClassConstructorInfo { @@ -178,7 +179,7 @@ namespace FreeSql.Internal { var typeGeneric = type; if (typeGeneric.FullName.StartsWith("System.Nullable`1[")) typeGeneric = type.GenericTypeArguments.First(); if (typeGeneric.IsEnum || - dicExecuteArrayRowReadClassOrTuple.ContainsKey(typeGeneric.FullName)) + dicExecuteArrayRowReadClassOrTuple.ContainsKey(typeGeneric)) return (GetDataReaderValue(type, row[dataIndex]), dataIndex + 1); if (type.Namespace == "System" && (type.FullName == "System.String" || type.IsValueType)) { //值类型,或者元组 bool isTuple = type.Name.StartsWith("ValueTuple`"); @@ -216,7 +217,7 @@ namespace FreeSql.Internal { if (names != null && names.TryGetValue(prop.Name, out tryidx) == false) continue; var read = ExecuteArrayRowReadClassOrTuple(prop.PropertyType, names, row, tryidx); if (read.dataIndex > dataIndex) dataIndex = read.dataIndex; - prop.SetValue(value, GetDataReaderValue(prop.PropertyType, read.value), null); + prop.SetValue(value, read.value, null); //FillPropertyValue(value, p.Name, read.value); //p.SetValue(value, read.value); } diff --git a/readme.md b/readme.md index 6e464af1..a1b21351 100644 --- a/readme.md +++ b/readme.md @@ -160,30 +160,29 @@ List t8 = fsql.Ado.Query("select * from song"); ## 性能测试 -### FreeSql ToList & Dapper Query - -Elapsed: 00:00:00.9666720; Query Entity Counts: 131072; ORM: Dapper - -Elapsed: 00:00:01.4215325; ToList Entity Counts: 131072; ORM: FreeSql* - ### FreeSql Query & Dapper Query -Elapsed: 00:00:00.9728656; Query Entity Counts: 131072; ORM: Dapper +Elapsed: 00:00:01.6999868; Query Entity Counts: 131072; ORM: Dapper -Elapsed: 00:00:00.4484073; Query Tuple Counts: 131072; ORM: Dapper +Elapsed: 00:00:00.4200430; Query Tuple Counts: 131072; ORM: Dapper -Elapsed: 00:00:00.6580620; Query Dynamic Counts: 131072; ORM: Dapper +Elapsed: 00:00:00.6615716; Query Dynamic Counts: 131072; ORM: Dapper -Elapsed: 00:00:02.6804199; Query Entity Counts: 131072; ORM: FreeSql* +Elapsed: 00:00:02.4955996; Query Entity Counts: 131072; ORM: FreeSql* -Elapsed: 00:00:01.4161527; Query Tuple Counts: 131072; ORM: FreeSql* +Elapsed: 00:00:01.2938320; Query Tuple Counts: 131072; ORM: FreeSql* -Elapsed: 00:00:00.9965082; Query Dynamic Counts: 131072; ORM: FreeSql* +Elapsed: 00:00:00.9719682; Query Dynamic Counts: 131072; ORM: FreeSql* + +### FreeSql ToList & Dapper Query + +Elapsed: 00:00:01.7446031; Query Entity Counts: 131072; ORM: Dapper + +Elapsed: 00:00:01.2857691; ToList Entity Counts: 131072; ORM: FreeSql* [查看测试代码](FreeSql.Tests.PerformanceTests/MySqlAdoTest.cs) -FreeSql 目前使用的反射+缓存,比不过 Dapper Emit 性能。 - +FreeSql 目前使用的反射+缓存,硬碰硬比不过 Dapper Emit,真实项目使用相差无几。Emit 在单次查询记录数量越多收益越大。 # Part2 添加 ```csharp