diff --git a/sdmap/Common.Version.props b/sdmap/Common.Version.props
new file mode 100644
index 0000000..702704d
--- /dev/null
+++ b/sdmap/Common.Version.props
@@ -0,0 +1,14 @@
+
+
+
+ 0.17.0
+
+
+
+ $(Version)
+ $(Version)
+ $(Version)
+ $(Version)
+
+
+
diff --git a/sdmap/sdmap.sln b/sdmap/sdmap.sln
index 2e5d55f..5c13963 100644
--- a/sdmap/sdmap.sln
+++ b/sdmap/sdmap.sln
@@ -12,6 +12,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
..\README.md = ..\README.md
..\ReleaseNotes.md = ..\ReleaseNotes.md
vstool.png = vstool.png
+ Common.Version.props = Common.Version.props
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{37A082F1-8179-4EEA-93D8-1C282B5AC65A}"
diff --git a/sdmap/src/sdmap.ext.Dapper/sdmap.ext.Dapper.csproj b/sdmap/src/sdmap.ext.Dapper/sdmap.ext.Dapper.csproj
index 2b0d125..8b87e65 100644
--- a/sdmap/src/sdmap.ext.Dapper/sdmap.ext.Dapper.csproj
+++ b/sdmap/src/sdmap.ext.Dapper/sdmap.ext.Dapper.csproj
@@ -1,5 +1,7 @@
+
+
net6.0;netstandard20
sdmap.ext.Dapper
@@ -14,13 +16,10 @@ https://github.com/sdcb/sdmap/blob/master/ReleaseNotes.md
false
false
false
- 0.16.5
Dapper extensions for sdmap.
https://github.com/sdcb/sdmap
sdcb
MIT
- 0.16.5
- 0.16.5
LICENSE
true
true
diff --git a/sdmap/src/sdmap.ext/sdmap.ext.csproj b/sdmap/src/sdmap.ext/sdmap.ext.csproj
index d6b0843..b3d61cc 100644
--- a/sdmap/src/sdmap.ext/sdmap.ext.csproj
+++ b/sdmap/src/sdmap.ext/sdmap.ext.csproj
@@ -1,5 +1,7 @@
+
+
net6.0;netstandard20
sdmap.ext
@@ -14,13 +16,10 @@ https://github.com/sdcb/sdmap/blob/master/ReleaseNotes.md
false
false
false
- 0.16.5
Useful extensions for sdmap/Dapper.
https://github.com/sdcb/sdmap
sdcb
MIT
- 0.16.5
- 0.16.5
LICENSE
true
true
diff --git a/sdmap/src/sdmap/IsExternalInit.cs b/sdmap/src/sdmap/IsExternalInit.cs
new file mode 100644
index 0000000..589db89
--- /dev/null
+++ b/sdmap/src/sdmap/IsExternalInit.cs
@@ -0,0 +1,8 @@
+// ReSharper disable CheckNamespace
+// ReSharper disable UnusedType.Global
+
+namespace System.Runtime.CompilerServices;
+
+internal sealed class IsExternalInit
+{
+}
\ No newline at end of file
diff --git a/sdmap/src/sdmap/Macros/Implements/PropertyMetadataRetriever.cs b/sdmap/src/sdmap/Macros/Implements/PropertyMetadataRetriever.cs
new file mode 100644
index 0000000..63ad896
--- /dev/null
+++ b/sdmap/src/sdmap/Macros/Implements/PropertyMetadataRetriever.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections;
+using System.Linq;
+
+namespace sdmap.Macros.Implements;
+
+using static PropertyMetadata;
+
+internal readonly record struct PropertyMetadata(string Name, object Value, bool Exists = true)
+{
+ public Type Type => Value?.GetType() ?? typeof(object);
+
+ public static PropertyMetadata DoesNotExist => new(string.Empty, default, false);
+
+ public static PropertyMetadata Root(object value) => new(string.Empty, value);
+}
+
+internal static class PropertyMetadataRetriever
+{
+ public static PropertyMetadata Get(object target, string propertyAccess)
+ {
+ if (string.IsNullOrWhiteSpace(propertyAccess))
+ {
+ return DoesNotExist;
+ }
+
+ return propertyAccess
+ .Split('.')
+ .Aggregate(Root(target), (metadata, next) => GetByKey(metadata.Value, next));
+ }
+
+ private static PropertyMetadata GetByKey(object target, string key)
+ => target switch
+ {
+ _ when string.IsNullOrWhiteSpace(key)
+ => DoesNotExist,
+
+ IDictionary dictionary
+ => dictionary.Contains(key)
+ ? new(key, dictionary[key])
+ : DoesNotExist,
+
+ not null
+ => GetByMemberName(target, key),
+
+ _ => DoesNotExist
+ };
+
+ private static PropertyMetadata GetByMemberName(object target, string memberName)
+ {
+ var type = target.GetType();
+
+ if (type.GetProperty(memberName) is { } property)
+ {
+ return new(memberName, property.GetValue(target));
+ }
+
+ if (type.GetField(memberName) is { } field)
+ {
+ return new(memberName, field.GetValue(target));
+ }
+
+ return DoesNotExist;
+ }
+}
\ No newline at end of file
diff --git a/sdmap/src/sdmap/Macros/Implements/RuntimeMacros.cs b/sdmap/src/sdmap/Macros/Implements/RuntimeMacros.cs
index c953d86..003edc7 100644
--- a/sdmap/src/sdmap/Macros/Implements/RuntimeMacros.cs
+++ b/sdmap/src/sdmap/Macros/Implements/RuntimeMacros.cs
@@ -4,7 +4,6 @@
using System;
using System.Collections;
using System.Linq;
-using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Text;
@@ -440,48 +439,16 @@ private static Result RequireType(Type type, string expected,
public static QueryPropertyInfo GetProp(object self, object syntax)
{
- var props = (syntax as string).Split('.');
- var fronts = props.Take(props.Length - 1);
-
- if (self is IDictionary dicSelf)
+ if (syntax is not string propertyAccess)
{
- if (!dicSelf.Contains(syntax))
- return null;
-
- var val = dicSelf[syntax];
- if (val == null)
- return new QueryPropertyInfo(props[0], typeof(object));
-
- return new QueryPropertyInfo(props[0], val.GetType());
+ throw new ArgumentException("Variable is expected to be of type string.", nameof(syntax));
}
- else
- {
- var frontValue = fronts.Aggregate(self, (s, p) =>
- s?.GetType().GetTypeInfo().GetProperty(p)?.GetValue(s));
-
- var pi = frontValue
- ?.GetType()
- .GetTypeInfo()
- .GetProperty(props.Last());
- if (pi == null) return null;
- return new QueryPropertyInfo(pi.Name, pi.PropertyType);
- }
+ var metadata = PropertyMetadataRetriever.Get(self, propertyAccess);
+ return metadata.Exists ? new(metadata.Name, metadata.Type) : null;
}
- public static object GetPropValue(object self, string prop)
- {
- if (self is IDictionary dicSelf)
- {
- return dicSelf[prop];
- }
- else
- {
- var props = prop.Split('.');
- return props.Aggregate(self, (s, p) =>
- s?.GetType().GetTypeInfo().GetProperty(p)?.GetValue(s));
- }
- }
+ public static object GetPropValue(object self, string prop) => PropertyMetadataRetriever.Get(self, prop).Value;
public static bool IsEmpty(object v)
{
diff --git a/sdmap/src/sdmap/sdmap.csproj b/sdmap/src/sdmap/sdmap.csproj
index 3ec42ae..f85af39 100644
--- a/sdmap/src/sdmap/sdmap.csproj
+++ b/sdmap/src/sdmap/sdmap.csproj
@@ -1,5 +1,7 @@
+
+
A template engine for writing dynamic sql.
net6;netstandard20
@@ -15,9 +17,6 @@
false
false
false
- 0.16.5
- 0.16.5
- 0.16.5
sdcb
MIT
https://github.com/sdcb/sdmap
diff --git a/sdmap/test/sdmap.test/PropertyMetadataRetrieverTests.cs b/sdmap/test/sdmap.test/PropertyMetadataRetrieverTests.cs
new file mode 100644
index 0000000..266b22d
--- /dev/null
+++ b/sdmap/test/sdmap.test/PropertyMetadataRetrieverTests.cs
@@ -0,0 +1,261 @@
+using System.Collections.Generic;
+using FluentAssertions;
+using sdmap.Macros.Implements;
+using Xunit;
+
+// ReSharper disable UnusedAutoPropertyAccessor.Local
+
+namespace sdmap.test;
+
+public class PropertyMetadataRetrieverTests
+{
+ [Fact]
+ public void NullObject_ReturnsNull()
+ {
+ var metadata = PropertyMetadataRetriever.Get(target: null, "Prop");
+
+ ShouldBeEmpty(metadata);
+ }
+
+ [Theory]
+ [InlineData(data: null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public void NullOrEmptyProp_ReturnsNull(string propertyAccess)
+ {
+ var target = new ComplexObject { Prop = "Test" };
+
+ var metadata = PropertyMetadataRetriever.Get(target, propertyAccess);
+
+ ShouldBeEmpty(metadata);
+ }
+
+ [Fact]
+ public void NonExistentProp_ReturnsNull()
+ {
+ var target = new ComplexObject { Prop = "Test" };
+
+ var metadata = PropertyMetadataRetriever.Get(target, "NonExistent");
+
+ ShouldBeEmpty(metadata);
+ }
+
+ [Fact]
+ public void ValidProp_ReturnsValue()
+ {
+ var target = new ComplexObject { Prop = "Test" };
+
+ var metadata = PropertyMetadataRetriever.Get(target, "Prop");
+
+ metadata.Name.Should().Be("Prop");
+ metadata.Value.Should().Be("Test");
+ metadata.Type.Should().Be(typeof(string));
+ }
+
+ [Fact]
+ public void ValidField_ReturnsValue()
+ {
+ var target = new ComplexObject { Field = "Test" };
+
+ var metadata = PropertyMetadataRetriever.Get(target, "Field");
+
+ metadata.Name.Should().Be("Field");
+ metadata.Value.Should().Be("Test");
+ metadata.Type.Should().Be(typeof(string));
+ }
+
+ [Fact]
+ public void NestedProp_ReturnsValue()
+ {
+ var target = new ComplexObject
+ {
+ Prop = new ComplexObject
+ {
+ Prop = "NestedTest"
+ }
+ };
+
+ var metadata = PropertyMetadataRetriever.Get(target, "Prop.Prop");
+
+ metadata.Name.Should().Be("Prop");
+ metadata.Value.Should().Be("NestedTest");
+ metadata.Type.Should().Be(typeof(string));
+ }
+
+ [Fact]
+ public void NestedPropAndField_ReturnsValue()
+ {
+ var target = new ComplexObject
+ {
+ Prop = new ComplexObject
+ {
+ Field = "NestedTest"
+ }
+ };
+
+ var metadata = PropertyMetadataRetriever.Get(target, "Prop.Field");
+
+ metadata.Name.Should().Be("Field");
+ metadata.Value.Should().Be("NestedTest");
+ metadata.Type.Should().Be(typeof(string));
+ }
+
+ [Fact]
+ public void Dictionary_ReturnsValue()
+ {
+ var target = new Dictionary
+ {
+ ["Key"] = "Value"
+ };
+
+ var metadata = PropertyMetadataRetriever.Get(target, "Key");
+
+ metadata.Name.Should().Be("Key");
+ metadata.Value.Should().Be("Value");
+ metadata.Type.Should().Be(typeof(string));
+ }
+
+ [Fact]
+ public void DictionaryNonExistentKey_ReturnsNull()
+ {
+ var target = new Dictionary
+ {
+ ["Key"] = "Value"
+ };
+
+ var metadata = PropertyMetadataRetriever.Get(target, "NonExistentKey");
+
+ ShouldBeEmpty(metadata);
+ }
+
+ [Fact]
+ public void DictionaryWithNestedDictionary_ReturnsValue()
+ {
+ var target = new Dictionary
+ {
+ ["Key"] = new Dictionary { ["NestedKey"] = "NestedValue" }
+ };
+
+ var metadata = PropertyMetadataRetriever.Get(target, "Key.NestedKey");
+
+ metadata.Name.Should().Be("NestedKey");
+ metadata.Value.Should().Be("NestedValue");
+ metadata.Type.Should().Be(typeof(string));
+ }
+
+ [Fact]
+ public void ObjectWithNestedDictionary_ReturnsValue()
+ {
+ var target = new ComplexObject
+ {
+ Prop = new Dictionary
+ {
+ ["DictionaryKey"] = "DictionaryValue"
+ }
+ };
+
+ var metadata = PropertyMetadataRetriever.Get(target, "Prop.DictionaryKey");
+
+ metadata.Name.Should().Be("DictionaryKey");
+ metadata.Value.Should().Be("DictionaryValue");
+ metadata.Type.Should().Be(typeof(string));
+ }
+
+ [Fact]
+ public void ObjectWithNestedDictionary_NonExistentKey_ReturnsNull()
+ {
+ var target = new ComplexObject
+ {
+ Prop = new Dictionary
+ {
+ ["DictionaryKey"] = "DictionaryValue"
+ }
+ };
+
+ var metadata = PropertyMetadataRetriever.Get(target, "Prop.NonExistentKey");
+
+ ShouldBeEmpty(metadata);
+ }
+
+ [Fact]
+ public void DictionaryWithNestedObject_ReturnsValue()
+ {
+ var target = new Dictionary
+ {
+ ["ObjectKey"] = new ComplexObject { Prop = new ComplexObject() }
+ };
+
+ var metadata = PropertyMetadataRetriever.Get(target, "ObjectKey.Prop");
+
+ metadata.Name.Should().Be("Prop");
+ metadata.Value.Should().BeEquivalentTo(new ComplexObject());
+ metadata.Type.Should().Be(typeof(ComplexObject));
+ }
+
+ [Fact]
+ public void DictionaryWithNestedObject_NonExistentProperty_ReturnsNull()
+ {
+ var target = new Dictionary
+ {
+ ["ObjectKey"] = new ComplexObject { Prop = "NestedObjectValue" }
+ };
+
+ var metadata = PropertyMetadataRetriever.Get(target, "ObjectKey.NonExistentProp");
+
+ ShouldBeEmpty(metadata);
+ }
+
+ [Fact]
+ public void ObjectWithNestedDictionaryAndObject_ReturnsValue()
+ {
+ var target = new ComplexObject
+ {
+ Prop = new Dictionary
+ {
+ ["NestedObjectKey"] = new ComplexObject { Prop = "NestedObjectValue" }
+ }
+ };
+
+ var metadata = PropertyMetadataRetriever.Get(target, "Prop.NestedObjectKey.Prop");
+
+ metadata.Name.Should().Be("Prop");
+ metadata.Value.Should().Be("NestedObjectValue");
+ metadata.Type.Should().Be(typeof(string));
+ }
+
+ [Fact]
+ public void DictionaryWithNestedObjectAndDictionary_ReturnsValue()
+ {
+ var target = new Dictionary
+ {
+ ["NestedObjectKey"] = new ComplexObject
+ {
+ Prop = new Dictionary
+ {
+ ["NestedDictionaryKey"] = "NestedDictionaryValue"
+ }
+ }
+ };
+
+ var metadata = PropertyMetadataRetriever.Get(target, "NestedObjectKey.Prop.NestedDictionaryKey");
+
+ metadata.Name.Should().Be("NestedDictionaryKey");
+ metadata.Value.Should().Be("NestedDictionaryValue");
+ metadata.Type.Should().Be(typeof(string));
+ }
+
+ private static void ShouldBeEmpty(PropertyMetadata metadata)
+ {
+ metadata.Name.Should().BeEmpty();
+ metadata.Value.Should().Be(null);
+ metadata.Type.Should().Be(typeof(object));
+ }
+
+ private sealed record ComplexObject
+ {
+ public object Prop { get; set; }
+
+ // ReSharper disable once NotAccessedField.Local
+ public object Field;
+ }
+}
\ No newline at end of file
diff --git a/sdmap/test/sdmap.test/sdmap.test.csproj b/sdmap/test/sdmap.test/sdmap.test.csproj
index 68ed873..991168a 100644
--- a/sdmap/test/sdmap.test/sdmap.test.csproj
+++ b/sdmap/test/sdmap.test/sdmap.test.csproj
@@ -5,6 +5,7 @@
+
all