From ec39a31d386c4deec100f9dc850200ec8ccca59e Mon Sep 17 00:00:00 2001
From: RzR <108324929+I-RzR-I@users.noreply.github.com>
Date: Thu, 9 Jan 2025 20:45:13 +0200
Subject: [PATCH] Adjust dynamic property/ies select avoid
System.Linq.Dynamic.Core
Adjust and rebuild dynamic propert select and dynamic result avoiding package reference.
---
.../ArraysExtensions/DynamicListExtensions.cs | 124 +++-
.../CommonExtensions/ReflectionExtensions.cs | 2 +-
.../CommonExtensions/TypeBuilderExtensions.cs | 106 ++++
.../DataTypeExtensions/StringExtensions.cs | 15 +
.../DomainCommonExtensions.csproj | 20 +-
.../Helpers/InsensitiveCaseHashtableHelper.cs | 2 +-
.../AnonymousSelect/Base/AnonymousClass.cs | 223 +++++++
.../AnonymousSelect/ExpressionHelper.cs | 178 ++++++
.../Factory/AnonymousClassFactory.cs | 553 ++++++++++++++++++
.../Factory/AssemblyBuilderFactory.cs | 59 ++
src/tests/DataTypeTests/DataTypeTests.csproj | 4 -
src/tests/DataTypeTests/DynamicListTests.cs | 289 +++++++++
src/tests/DataTypeTests/Models/RecordType.cs | 25 +
src/tests/DataTypeTests/Models/TempModel.cs | 20 +
14 files changed, 1597 insertions(+), 23 deletions(-)
create mode 100644 src/DomainCommonExtensions/CommonExtensions/TypeBuilderExtensions.cs
create mode 100644 src/DomainCommonExtensions/Helpers/Internal/AnonymousSelect/Base/AnonymousClass.cs
create mode 100644 src/DomainCommonExtensions/Helpers/Internal/AnonymousSelect/ExpressionHelper.cs
create mode 100644 src/DomainCommonExtensions/Helpers/Internal/AnonymousSelect/Factory/AnonymousClassFactory.cs
create mode 100644 src/DomainCommonExtensions/Helpers/Internal/AnonymousSelect/Factory/AssemblyBuilderFactory.cs
create mode 100644 src/tests/DataTypeTests/DynamicListTests.cs
create mode 100644 src/tests/DataTypeTests/Models/RecordType.cs
diff --git a/src/DomainCommonExtensions/ArraysExtensions/DynamicListExtensions.cs b/src/DomainCommonExtensions/ArraysExtensions/DynamicListExtensions.cs
index bbc6626..5b7983e 100644
--- a/src/DomainCommonExtensions/ArraysExtensions/DynamicListExtensions.cs
+++ b/src/DomainCommonExtensions/ArraysExtensions/DynamicListExtensions.cs
@@ -18,9 +18,14 @@
using System.Collections.Generic;
using System.Linq;
-using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
using DomainCommonExtensions.CommonExtensions;
+using DomainCommonExtensions.Helpers.Internal.AnonymousSelect;
+
+#region OLD Using System.Linq.Dynamic.Core
+//using System.Linq.Dynamic.Core;
+//using System.Linq.Dynamic.Core.Parser;
+#endregion
#endregion
@@ -32,8 +37,57 @@ namespace DomainCommonExtensions.ArraysExtensions
///
public static class DynamicListExtensions
{
+ #region OLD Using System.Linq.Dynamic.Core
+
+ /*
+ // Using System.Linq.Dynamic.Core
+ // //
+ // // Parse input data (List) to dynamic result (list)
+ // //
+ // // List of T input data
+ // // Required fields
+ // //
+ // //
+ // //
+ // public static IList ParseListOfTInDynamic(this List input, IEnumerable fields = null)
+ // {
+ // var data = input.AsQueryable();
+ // var requiredFields = typeof(T).GetSelectedFieldFromEntity(fields);
+ //
+ // var result = data.Select("new {" + requiredFields + "}").ToDynamicList();
+ //
+ // return result;
+ // }
+
+ */
+
+ /*
+ // Using System.Linq.Dynamic.Core
+ // ///
+ // /// Parse input data (IEnumerable) to dynamic result (list)
+ // ///
+ // /// IEnumerable of T input data
+ // /// Required fields
+ // ///
+ // ///
+ // ///
+ // public static IList ParseEnumerableOfTInDynamic(this IEnumerable input,
+ // IEnumerable fields = null)
+ // {
+ // var data = input.AsQueryable();
+ // var requiredFields = typeof(T).GetSelectedFieldFromEntity(fields);
+
+ // var result = data.Select("new {" + requiredFields + "}").ToDynamicList();
+
+ // return result;
+ // }
+
+ */
+
+ #endregion
+
///
- /// Parse input data (List) to dynamic result (list)
+ /// Parse input data (IEnumerable) to dynamic result (list)
///
/// List of T input data
/// Required fields
@@ -44,29 +98,45 @@ public static IList ParseListOfTInDynamic(this List input, IEnume
{
var data = input.AsQueryable();
var requiredFields = typeof(T).GetSelectedFieldFromEntity(fields);
+ var parameter = Expression.Parameter(data.ElementType, "x");
+ var lambdaExpression = ExpressionHelper.ParseLambda(parameter, data.ElementType, false, requiredFields.Split(','));
+
+ var selectCall = Expression.Call(
+ typeof(Queryable),
+ "Select",
+ new[] { parameter.Type, lambdaExpression.Body.Type/*source.ElementType*/ },
+ data.Expression,
+ Expression.Quote(lambdaExpression));
- var result = data.Select("new {" + requiredFields + "}").ToDynamicList();
+ var result = data.Provider.CreateQuery(selectCall);
- return result;
+ return result.Cast().ToList();
}
-
///
- /// Parse input data (IEnumerable) to dynamic result (list)
+ /// Parse input data (List) to dynamic result (list)
///
/// IEnumerable of T input data
/// Required fields
///
///
///
- public static IList ParseEnumerableOfTInDynamic(this IEnumerable input,
- IEnumerable fields = null)
+ public static IList ParseEnumerableOfTInDynamic(this IEnumerable input, IEnumerable fields = null)
{
var data = input.AsQueryable();
var requiredFields = typeof(T).GetSelectedFieldFromEntity(fields);
+ var parameter = Expression.Parameter(data.ElementType, "x");
+ var lambdaExpression = ExpressionHelper.ParseLambda(parameter, data.ElementType, false, requiredFields.Split(','));
- var result = data.Select("new {" + requiredFields + "}").ToDynamicList();
+ var selectCall = Expression.Call(
+ typeof(Queryable),
+ "Select",
+ new[] { parameter.Type, lambdaExpression.Body.Type/*source.ElementType*/ },
+ data.Expression,
+ Expression.Quote(lambdaExpression));
+
+ var result = data.Provider.CreateQuery(selectCall);
- return result;
+ return result.Cast().ToList();
}
///
@@ -79,12 +149,36 @@ public static IList ParseEnumerableOfTInDynamic(this IEnumerable
public static IQueryable SelectProperty(this IQueryable source, string prop)
{
var parameter = Expression.Parameter(source.ElementType, "x");
- var property = prop.Split(',')
- .Aggregate((Expression)parameter, Expression.Property);
- var selector = Expression.Lambda(property, parameter);
+ var propEx = Expression.Property(parameter, prop);
+ var selector = Expression.Lambda(propEx, parameter);
+ var selectCall = Expression.Call(
+ typeof(Queryable),
+ "Select",
+ new[] { parameter.Type, propEx.Type },
+ source.Expression,
+ Expression.Quote(selector));
+
+ return source.Provider.CreateQuery(selectCall);
+ }
+
+ ///
+ /// Generate select multiple properties
+ ///
+ /// Data source IQueryable
+ /// Properties name
+ ///
+ ///
+ public static IQueryable SelectMultipleProperties(this IQueryable source, params string[] props)
+ {
+ var parameter = Expression.Parameter(source.ElementType, "x");
+ var lambdaExpression = ExpressionHelper.ParseLambda(parameter, source.ElementType, true, props);
+
var selectCall = Expression.Call(
- typeof(Queryable), "Select", new[] { parameter.Type, property.Type },
- source.Expression, Expression.Quote(selector));
+ typeof(Queryable),
+ "Select",
+ new[] { parameter.Type, lambdaExpression.Body.Type/*source.ElementType*/ },
+ source.Expression,
+ Expression.Quote(lambdaExpression));
return source.Provider.CreateQuery(selectCall);
}
diff --git a/src/DomainCommonExtensions/CommonExtensions/ReflectionExtensions.cs b/src/DomainCommonExtensions/CommonExtensions/ReflectionExtensions.cs
index 9a7a089..56e146e 100644
--- a/src/DomainCommonExtensions/CommonExtensions/ReflectionExtensions.cs
+++ b/src/DomainCommonExtensions/CommonExtensions/ReflectionExtensions.cs
@@ -52,7 +52,7 @@ public static void CopyProperties(this object source, object destination)
var targetProperty = typeDest.GetProperty(srcProp.Name);
if (targetProperty.IsNull())
continue;
- if (!targetProperty.CanWrite)
+ if (!targetProperty!.CanWrite)
continue;
if (targetProperty.GetSetMethod(true) != null && targetProperty.GetSetMethod(true).IsPrivate)
continue;
diff --git a/src/DomainCommonExtensions/CommonExtensions/TypeBuilderExtensions.cs b/src/DomainCommonExtensions/CommonExtensions/TypeBuilderExtensions.cs
new file mode 100644
index 0000000..38f9565
--- /dev/null
+++ b/src/DomainCommonExtensions/CommonExtensions/TypeBuilderExtensions.cs
@@ -0,0 +1,106 @@
+// ***********************************************************************
+// Assembly : RzR.Shared.Extensions.DomainCommonExtensions
+// Author : RzR
+// Created On : 2025-01-08 09:27
+//
+// Last Modified By : RzR
+// Last Modified On : 2025-01-08 09:28
+// ***********************************************************************
+//
+// Copyright © RzR. All rights reserved.
+//
+//
+//
+//
+// ***********************************************************************
+
+#region U S A G E S
+
+// ReSharper disable RedundantUsingDirective
+
+using System;
+using System.Reflection;
+using System.Reflection.Emit;
+
+#endregion
+
+namespace DomainCommonExtensions.CommonExtensions
+{
+ /// -------------------------------------------------------------------------------------------------
+ ///
+ /// A type builder extensions.
+ ///
+ /// =================================================================================================
+ public static class TypeBuilderExtensions
+ {
+
+#if !(NET35 || NET40 || SILVERLIGHT || WPSL || UAP10_0)
+
+ /// -------------------------------------------------------------------------------------------------
+ ///
+ /// Creates a System.Type object for the class. After defining fields and methods
+ /// on the class, CreateType is called in order to load its Type object.
+ ///
+ /// The TypeBuilder to act on.
+ ///
+ /// Returns the new System.Type object for this class.
+ ///
+ /// =================================================================================================
+ public static Type CreateType(this TypeBuilder tb)
+ {
+ return tb.CreateTypeInfo().AsType();
+ }
+#endif
+
+#if NET35 || NET40 || SILVERLIGHT || WPSL || NETCOREAPP || NETSTANDARD2_1
+
+ /// -------------------------------------------------------------------------------------------------
+ ///
+ /// A TypeBuilder extension method that define property.
+ ///
+ /// The TypeBuilder to act on.
+ /// The name.
+ /// The attributes.
+ /// The calling convention.
+ /// Type of the return.
+ /// List of types of the parameters.
+ ///
+ /// A PropertyBuilder.
+ ///
+ /// =================================================================================================
+ public static PropertyBuilder DefineProperty(this TypeBuilder tb, string name, PropertyAttributes attributes,
+ CallingConventions callingConvention, Type returnType, Type[] parameterTypes)
+ {
+ return tb.DefineProperty(name, attributes, returnType, parameterTypes);
+ }
+
+ /// -------------------------------------------------------------------------------------------------
+ ///
+ /// A TypeBuilder extension method that converts a builder to a type.
+ ///
+ /// The builder to act on.
+ ///
+ /// A Type.
+ ///
+ /// =================================================================================================
+ public static Type AsType(this TypeBuilder builder)
+ {
+ return builder;
+ }
+
+ /// -------------------------------------------------------------------------------------------------
+ ///
+ /// A GenericTypeParameterBuilder extension method that converts a builder to a type.
+ ///
+ /// The builder to act on.
+ ///
+ /// A Type.
+ ///
+ /// =================================================================================================
+ public static Type AsType(this GenericTypeParameterBuilder builder)
+ {
+ return builder;
+ }
+#endif
+ }
+}
\ No newline at end of file
diff --git a/src/DomainCommonExtensions/DataTypeExtensions/StringExtensions.cs b/src/DomainCommonExtensions/DataTypeExtensions/StringExtensions.cs
index 3af6aa1..3d2e1da 100644
--- a/src/DomainCommonExtensions/DataTypeExtensions/StringExtensions.cs
+++ b/src/DomainCommonExtensions/DataTypeExtensions/StringExtensions.cs
@@ -1574,5 +1574,20 @@ public static bool IsValidJsonArray(this string source)
}
#endif
+
+ ///
+ /// Escape backslash from source string
+ ///
+ /// Source string
+ /// Custom char to be escaped in source. Default value is '|'.
+ ///
+ /// Replace by default '\' => '\\', and custom char(string value) from 'x' => '\x'
+ public static string EscapeBackSlash(this string source, string customCharEscape = "|")
+ {
+ source = source.Replace(@"\", @"\\");
+ source = source.Replace(customCharEscape, @$"\{customCharEscape}");
+
+ return source;
+ }
}
}
\ No newline at end of file
diff --git a/src/DomainCommonExtensions/DomainCommonExtensions.csproj b/src/DomainCommonExtensions/DomainCommonExtensions.csproj
index f80cdbc..8585f3e 100644
--- a/src/DomainCommonExtensions/DomainCommonExtensions.csproj
+++ b/src/DomainCommonExtensions/DomainCommonExtensions.csproj
@@ -50,8 +50,8 @@
-
-
+
+
@@ -66,6 +66,10 @@
+
+
+
+
@@ -74,6 +78,18 @@
+
+
+ 4.2.0
+
+
+
+
+
+ 4.2.0
+
+
+
diff --git a/src/DomainCommonExtensions/Helpers/InsensitiveCaseHashtableHelper.cs b/src/DomainCommonExtensions/Helpers/InsensitiveCaseHashtableHelper.cs
index 9539d34..0c2c88f 100644
--- a/src/DomainCommonExtensions/Helpers/InsensitiveCaseHashtableHelper.cs
+++ b/src/DomainCommonExtensions/Helpers/InsensitiveCaseHashtableHelper.cs
@@ -51,7 +51,7 @@ public ArrayList Collection
{
var arg = arrayList;
if (enumerator.Current.IsNull()) continue;
- var dictionaryEntry = (DictionaryEntry)enumerator.Current;
+ var dictionaryEntry = (DictionaryEntry)enumerator.Current!;
arg.Add(dictionaryEntry.Value);
}
diff --git a/src/DomainCommonExtensions/Helpers/Internal/AnonymousSelect/Base/AnonymousClass.cs b/src/DomainCommonExtensions/Helpers/Internal/AnonymousSelect/Base/AnonymousClass.cs
new file mode 100644
index 0000000..2500b64
--- /dev/null
+++ b/src/DomainCommonExtensions/Helpers/Internal/AnonymousSelect/Base/AnonymousClass.cs
@@ -0,0 +1,223 @@
+// ***********************************************************************
+// Assembly : RzR.Shared.Extensions.DomainCommonExtensions
+// Author : RzR
+// Created On : 2025-01-08 13:29
+//
+// Last Modified By : RzR
+// Last Modified On : 2025-01-08 18:05
+// ***********************************************************************
+//
+// Copyright © RzR. All rights reserved.
+//
+//
+//
+//
+// ***********************************************************************
+
+#region U S A G E S
+
+using System.Collections.Generic;
+using System.Dynamic;
+using System.Reflection;
+using CodeSource;
+using DomainCommonExtensions.CommonExtensions;
+
+#endregion
+
+namespace DomainCommonExtensions.Helpers.Internal.AnonymousSelect.Base
+{
+ /// -------------------------------------------------------------------------------------------------
+ ///
+ /// The anonymous class.
+ ///
+ ///
+ /// =================================================================================================
+ [CodeSource(SourceUrl = "System.Linq.Dynamic.Core.DynamicClass", Version = 1.0D, Comment = "The current implementation has inspiration from a specified source URL.")]
+ public abstract class AnonymousClass : DynamicObject
+ {
+ /// -------------------------------------------------------------------------------------------------
+ ///
+ /// Dictionary of properties.
+ ///
+ /// =================================================================================================
+ private Dictionary _propertiesDictionary;
+
+ /// -------------------------------------------------------------------------------------------------
+ ///
+ /// Gets the properties.
+ ///
+ ///
+ /// The properties.
+ ///
+ /// =================================================================================================
+ private Dictionary Properties
+ {
+ get
+ {
+ if (_propertiesDictionary.IsNull())
+ {
+ _propertiesDictionary = new Dictionary();
+
+ foreach (var pi in GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
+ {
+ var parameters = pi.GetIndexParameters().Length;
+ if (parameters > 0)
+ // The property is an indexer, skip this.
+ continue;
+
+ _propertiesDictionary.Add(pi.Name, pi.GetValue(this, null));
+ }
+ }
+
+ return _propertiesDictionary;
+ }
+ }
+
+ /// -------------------------------------------------------------------------------------------------
+ ///
+ /// Gets or sets the with the specified name.
+ ///
+ /// The name.
+ /// The .
+ /// Value from the property.
+ /// =================================================================================================
+ public object this[string name]
+ {
+ get => Properties.TryGetValue(name, out var result) ? result : null;
+
+ set => Properties[name] = value;
+ }
+
+ /// -------------------------------------------------------------------------------------------------
+ ///
+ /// Gets the dynamic property by name.
+ ///
+ /// The type.
+ /// Name of the property.
+ ///
+ /// T.
+ ///
+ /// =================================================================================================
+ public T GetDynamicPropertyValue(string propertyName)
+ {
+ var type = GetType();
+ var propInfo = type.GetProperty(propertyName);
+
+ return (T)propInfo?.GetValue(this, null);
+ }
+
+ /// -------------------------------------------------------------------------------------------------
+ ///
+ /// Gets the dynamic property value by name.
+ ///
+ /// Name of the property.
+ ///
+ /// value.
+ ///
+ /// =================================================================================================
+ public object GetDynamicPropertyValue(string propertyName) => GetDynamicPropertyValue
// ***********************************************************************
+using System;
+
namespace DataTypeTests.Models
{
public class TempModel
{
public int Id { get; set; }
+
public string Name { get; set; }
+
public string Code { get; set; }
+
+ public DateTime CreatedAt { get; set; }
+
+ public DateTime? DeletedAt { get; set; }
+
+ public decimal Version { get; set; }
+
+ public decimal? Price { get; set; }
+
+ public RecordType RecordType { get; set; }
+
+ public bool IsActive { get; set; }
+
+ public double DblValue1 { get; set; }
+
+ public double? DblValue2 { get; set; }
}
}
\ No newline at end of file