From c4c81b0b0f0d0defa6e8c15b17eb53d672bbb74d Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Mon, 25 Feb 2019 12:44:06 +0200 Subject: [PATCH 01/11] Use compiled expression in AliasToBeanResultTransformer --- .../AliasToBeanResultTransformerFixture.cs | 18 +- .../AliasToBeanResultTransformerFixture.cs | 18 +- .../Transform/AliasToBeanResultTransformer.cs | 185 +++++++++--------- src/NHibernate/Transform/Transformers.cs | 2 + 4 files changed, 120 insertions(+), 103 deletions(-) diff --git a/src/NHibernate.Test/Async/TransformTests/AliasToBeanResultTransformerFixture.cs b/src/NHibernate.Test/Async/TransformTests/AliasToBeanResultTransformerFixture.cs index fbe948fb8c1..8edd1ee074c 100644 --- a/src/NHibernate.Test/Async/TransformTests/AliasToBeanResultTransformerFixture.cs +++ b/src/NHibernate.Test/Async/TransformTests/AliasToBeanResultTransformerFixture.cs @@ -8,7 +8,6 @@ //------------------------------------------------------------------------------ -using System.Collections; using System.Reflection; using NHibernate.Transform; using NHibernate.Util; @@ -225,7 +224,7 @@ public async Task WorksWithManyCandidatesAsync() { using (var s = OpenSession()) { - var transformer = Transformers.AliasToBean(); + var transformer = GetTransformer(); var l = await (s.CreateSQLQuery("select id as ID, Name as NamE from Simple") .SetResultTransformer(transformer) .ListAsync()); @@ -244,7 +243,7 @@ public void ToPropertiesInsensitivelyDuplicated_WithoutAnyProjectionsAsync() { using (var s = OpenSession()) { - var transformer = Transformers.AliasToBean(); + var transformer = GetTransformer(); Assert.ThrowsAsync(() => { return s.CreateSQLQuery("select * from Simple") @@ -268,7 +267,7 @@ public async Task SerializationAsync() { using (var s = OpenSession()) { - transformer = transformer ?? Transformers.AliasToBean(); + transformer = transformer ?? GetTransformer(); var l = await (s.CreateSQLQuery("select * from Simple") .SetResultTransformer(transformer) .ListAsync(cancellationToken)); @@ -285,7 +284,7 @@ public async Task SerializationAsync() { using (var s = OpenSession()) { - var transformer = Transformers.AliasToBean(); + var transformer = GetTransformer(); var l = await (s.CreateSQLQuery(queryString) .SetResultTransformer(transformer) .ListAsync(cancellationToken)); @@ -301,9 +300,9 @@ public async Task SerializationAsync() { try { - var transformer = Transformers.AliasToBean(); + var transformer = GetTransformer(); var bytes = SerializationHelper.Serialize(transformer); - transformer = (IResultTransformer)SerializationHelper.Deserialize(bytes); + transformer = (IResultTransformer) SerializationHelper.Deserialize(bytes); return AssertCardinalityNameAndIdAsync(transformer: transformer, cancellationToken: cancellationToken); } catch (System.Exception ex) @@ -311,5 +310,10 @@ public async Task SerializationAsync() return Task.FromException(ex); } } + + protected IResultTransformer GetTransformer() + { + return Transformers.AliasToBean(); + } } } diff --git a/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs b/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs index c8ea773a7fc..8d78567f614 100644 --- a/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs +++ b/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs @@ -1,4 +1,3 @@ -using System.Collections; using System.Reflection; using NHibernate.Transform; using NHibernate.Util; @@ -213,7 +212,7 @@ public void WorksWithManyCandidates() { using (var s = OpenSession()) { - var transformer = Transformers.AliasToBean(); + var transformer = GetTransformer(); var l = s.CreateSQLQuery("select id as ID, Name as NamE from Simple") .SetResultTransformer(transformer) .List(); @@ -232,7 +231,7 @@ public void ToPropertiesInsensitivelyDuplicated_WithoutAnyProjections() { using (var s = OpenSession()) { - var transformer = Transformers.AliasToBean(); + var transformer = GetTransformer(); Assert.Throws(() => { s.CreateSQLQuery("select * from Simple") @@ -256,7 +255,7 @@ private void AssertCardinalityNameAndId(IResultTransformer transformer = null { using (var s = OpenSession()) { - transformer = transformer ?? Transformers.AliasToBean(); + transformer = transformer ?? GetTransformer(); var l = s.CreateSQLQuery("select * from Simple") .SetResultTransformer(transformer) .List(); @@ -273,7 +272,7 @@ private void AssertCardinalityAndSomething(string queryString = "select s.Nam { using (var s = OpenSession()) { - var transformer = Transformers.AliasToBean(); + var transformer = GetTransformer(); var l = s.CreateSQLQuery(queryString) .SetResultTransformer(transformer) .List(); @@ -287,10 +286,15 @@ private void AssertCardinalityAndSomething(string queryString = "select s.Nam private void AssertSerialization() { - var transformer = Transformers.AliasToBean(); + var transformer = GetTransformer(); var bytes = SerializationHelper.Serialize(transformer); - transformer = (IResultTransformer)SerializationHelper.Deserialize(bytes); + transformer = (IResultTransformer) SerializationHelper.Deserialize(bytes); AssertCardinalityNameAndId(transformer: transformer); } + + protected IResultTransformer GetTransformer() + { + return Transformers.AliasToBean(); + } } } diff --git a/src/NHibernate/Transform/AliasToBeanResultTransformer.cs b/src/NHibernate/Transform/AliasToBeanResultTransformer.cs index cabbfb97ca0..6b3b9233d99 100644 --- a/src/NHibernate/Transform/AliasToBeanResultTransformer.cs +++ b/src/NHibernate/Transform/AliasToBeanResultTransformer.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Reflection; using System.Runtime.Serialization; using NHibernate.Util; @@ -12,6 +13,7 @@ namespace NHibernate.Transform /// Result transformer that allows to transform a result to /// a user specified class which will be populated via setter /// methods or fields matching the alias names. + /// NOTE: This transformer can't be reused by different queries as it caches query aliases on first transformation /// /// /// @@ -36,31 +38,23 @@ namespace NHibernate.Transform public class AliasToBeanResultTransformer : AliasedTupleSubsetResultTransformer, IEquatable { private readonly System.Type _resultClass; - private readonly ConstructorInfo _beanConstructor; private readonly Dictionary> _fieldsByNameCaseSensitive; private readonly Dictionary> _fieldsByNameCaseInsensitive; private readonly Dictionary> _propertiesByNameCaseSensitive; private readonly Dictionary> _propertiesByNameCaseInsensitive; + [NonSerialized] + Func _objectIniter; + + public System.Type ResultClass => _resultClass; + public AliasToBeanResultTransformer(System.Type resultClass) { _resultClass = resultClass ?? throw new ArgumentNullException("resultClass"); - const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; - _beanConstructor = resultClass.GetConstructor(bindingFlags, null, System.Type.EmptyTypes, null); - - // if resultClass is a ValueType (struct), GetConstructor will return null... - // in that case, we'll use Activator.CreateInstance instead of the ConstructorInfo to create instances - if (_beanConstructor == null && resultClass.IsClass) - { - throw new ArgumentException( - "The target class of a AliasToBeanResultTransformer need a parameter-less constructor", - nameof(resultClass)); - } - var fields = new List>(); var properties = new List>(); - FetchFieldsAndProperties(fields, properties); + FetchFieldsAndProperties(resultClass, fields, properties); _fieldsByNameCaseSensitive = GetMapByName(fields, StringComparer.Ordinal); _fieldsByNameCaseInsensitive = GetMapByName(fields, StringComparer.OrdinalIgnoreCase); @@ -73,35 +67,10 @@ public override bool IsTransformedValueATupleElement(String[] aliases, int tuple return false; } - public override object TransformTuple(object[] tuple, String[] aliases) + public override object TransformTuple(object[] tuple, string[] aliases) { - if (aliases == null) - { - throw new ArgumentNullException("aliases"); - } - object result; - - try - { - result = _resultClass.IsClass - ? _beanConstructor.Invoke(null) - : Activator.CreateInstance(_resultClass, true); - - for (int i = 0; i < aliases.Length; i++) - { - SetProperty(aliases[i], tuple[i], result); - } - } - catch (InstantiationException e) - { - throw new HibernateException("Could not instantiate result class: " + _resultClass.FullName, e); - } - catch (MethodAccessException e) - { - throw new HibernateException("Could not instantiate result class: " + _resultClass.FullName, e); - } - - return result; + var initer = _objectIniter ?? (_objectIniter = CompileObjectIniter(aliases)); + return initer(tuple); } public override IList TransformList(IList collection) @@ -111,54 +80,26 @@ public override IList TransformList(IList collection) #region Setter resolution - /// - /// Set the value of a property or field matching an alias. - /// - /// The alias for which resolving the property or field. - /// The value to which the property or field should be set. - /// The object on which to set the property or field. It must be of the type for which - /// this instance has been built. - /// Thrown if no matching property or field can be found. - /// Thrown if many matching properties or fields are found, having the - /// same visibility and inheritance depth. - private void SetProperty(string alias, object value, object resultObj) + protected MemberInfo GetMemberInfo(string alias) { - if (alias == null) - // Grouping properties in criteria are selected without alias, just ignore them. - return; - - if (TrySet(alias, value, resultObj, _propertiesByNameCaseSensitive)) - return; - if (TrySet(alias, value, resultObj, _fieldsByNameCaseSensitive)) - return; - if (TrySet(alias, value, resultObj, _propertiesByNameCaseInsensitive)) - return; - if (TrySet(alias, value, resultObj, _fieldsByNameCaseInsensitive)) - return; - - throw new PropertyNotFoundException(resultObj.GetType(), alias, "setter"); - } - - private bool TrySet(string alias, object value, object resultObj, Dictionary> fieldsMap) - { - if (fieldsMap.TryGetValue(alias, out var field)) - { - CheckMember(field, alias); - field.Member.SetValue(resultObj, value); - return true; - } - return false; + return TryGetMemberInfo(alias, _propertiesByNameCaseSensitive) + ?? TryGetMemberInfo(alias, _fieldsByNameCaseSensitive) + ?? TryGetMemberInfo(alias, _propertiesByNameCaseInsensitive) + ?? (MemberInfo) TryGetMemberInfo(alias, _fieldsByNameCaseInsensitive) + ?? throw new PropertyNotFoundException(_resultClass, alias, "setter"); } - private bool TrySet(string alias, object value, object resultObj, Dictionary> propertiesMap) + private TMemberInfo TryGetMemberInfo(string alias, params Dictionary>[] propertiesMaps) where TMemberInfo: MemberInfo { - if (propertiesMap.TryGetValue(alias, out var property)) + foreach (var propertiesMap in propertiesMaps) { - CheckMember(property, alias); - property.Member.SetValue(resultObj, value); - return true; + if (propertiesMap.TryGetValue(alias, out var property)) + { + CheckMember(property, alias); + return property.Member; + } } - return false; + return null; } private void CheckMember(NamedMember member, string alias) where T : MemberInfo @@ -179,10 +120,74 @@ private void CheckMember(NamedMember member, string alias) where T : Membe $"{string.Join(", ", member.AmbiguousMembers.Select(m => m.Name))}"); } - private void FetchFieldsAndProperties(List> fields, List> properties) + private Func CompileObjectIniter(string[] aliases) + { + if (aliases == null) + { + throw new ArgumentNullException("aliases"); + } + + var bindings = new List(aliases.Length); + var tupleParam = Expression.Parameter(typeof(object[]), "tuple"); + for (int i = 0; i < aliases.Length; i++) + { + string alias = aliases[i]; + if (string.IsNullOrEmpty(alias)) + continue; + + var memberInfo = GetMemberInfo(alias); + var valueExpr = Expression.ArrayAccess(tupleParam, Expression.Constant(i)); + bindings.Add(Expression.Bind(memberInfo, GetTyped(memberInfo, valueExpr))); + } + + Expression initExpr = Expression.MemberInit(GetNewExpression(ResultClass), bindings); + if (!ResultClass.IsClass) + initExpr = Expression.Convert(initExpr, typeof(object)); + + return (Func) Expression.Lambda(initExpr, tupleParam).Compile(); + } + + private static Expression GetTyped(MemberInfo memberInfo, Expression expr) + { + var type = GetMemberType(memberInfo); + if (type == typeof(object)) + return expr; + return Expression.Convert(expr, type); + } + + private static System.Type GetMemberType(MemberInfo memberInfo) + { + if (memberInfo is PropertyInfo prop) + return prop.PropertyType; + + if (memberInfo is FieldInfo field) + return field.FieldType; + + throw new NotSupportedException($"Member type {memberInfo} is not supported"); + } + + private static NewExpression GetNewExpression(System.Type resultClass) + { + if (!resultClass.IsClass) + return Expression.New(resultClass); + + const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + var beanConstructor = resultClass.GetConstructor(bindingFlags, null, System.Type.EmptyTypes, null); + + if (beanConstructor == null) + { + throw new ArgumentException( + "The target class of a AliasToBeanResultTransformer need a parameter-less constructor", + nameof(resultClass)); + } + + return Expression.New(beanConstructor); + } + + private static void FetchFieldsAndProperties(System.Type resultClass, List> fields, List> properties) { const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly; - var currentType = _resultClass; + var currentType = resultClass; var rank = 1; // For grasping private members, we need to manually walk the hierarchy. while (currentType != null && currentType != typeof(object)) @@ -201,7 +206,7 @@ private void FetchFieldsAndProperties(List> fields, List } } - private int GetFieldVisibilityRank(FieldInfo field) + private static int GetFieldVisibilityRank(FieldInfo field) { if (field.IsPublic) return 1; @@ -214,7 +219,7 @@ private int GetFieldVisibilityRank(FieldInfo field) return 5; } - private int GetPropertyVisibilityRank(PropertyInfo property) + private static int GetPropertyVisibilityRank(PropertyInfo property) { var setter = property.SetMethod; if (setter.IsPublic) @@ -228,7 +233,7 @@ private int GetPropertyVisibilityRank(PropertyInfo property) return 5; } - private Dictionary> GetMapByName(IEnumerable> members, StringComparer comparer) where T : MemberInfo + private static Dictionary> GetMapByName(IEnumerable> members, StringComparer comparer) where T : MemberInfo { return members .GroupBy(m => m.Member.Name, @@ -310,6 +315,8 @@ public bool Equals(AliasToBeanResultTransformer other) { return true; } + if (GetType() != other.GetType()) + return false; return Equals(other._resultClass, _resultClass); } diff --git a/src/NHibernate/Transform/Transformers.cs b/src/NHibernate/Transform/Transformers.cs index e139c709e8f..ec62e5cc9d8 100644 --- a/src/NHibernate/Transform/Transformers.cs +++ b/src/NHibernate/Transform/Transformers.cs @@ -15,6 +15,7 @@ public static class Transformers /// /// Creates a result transformer that will inject aliased values into instances /// of via property methods or fields. + /// NOTE: This transformer can't be reused by different queries as it caches query aliases on first transformation /// /// The type of the instances to build. /// A result transformer for supplied type. @@ -31,6 +32,7 @@ public static IResultTransformer AliasToBean(System.Type target) /// /// Creates a result transformer that will inject aliased values into instances /// of via property methods or fields. + /// NOTE: This transformer can't be reused by different queries as it caches query aliases on first transformation /// /// The type of the instances to build. /// A result transformer for supplied type. From dc8d4c3e7cdecbd94adbbc34f1a0396cc1bb65a8 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Mon, 25 Feb 2019 13:39:38 +0200 Subject: [PATCH 02/11] null -> default(T) --- src/NHibernate/Transform/AliasToBeanResultTransformer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/NHibernate/Transform/AliasToBeanResultTransformer.cs b/src/NHibernate/Transform/AliasToBeanResultTransformer.cs index 6b3b9233d99..44722557a8d 100644 --- a/src/NHibernate/Transform/AliasToBeanResultTransformer.cs +++ b/src/NHibernate/Transform/AliasToBeanResultTransformer.cs @@ -152,6 +152,10 @@ private static Expression GetTyped(MemberInfo memberInfo, Expression expr) var type = GetMemberType(memberInfo); if (type == typeof(object)) return expr; + if (!type.IsClass) + { + expr = Expression.Coalesce(expr, Expression.Default(type)); + } return Expression.Convert(expr, type); } From bcb4c1f298279d075edfcf84cdafbcee25b7f745 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Mon, 25 Feb 2019 22:59:26 +0200 Subject: [PATCH 03/11] more tests --- .../AliasToBeanResultTransformerFixture.cs | 26 ++++++++ .../AliasToBeanResultTransformerFixture.cs | 66 +++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/src/NHibernate.Test/Async/TransformTests/AliasToBeanResultTransformerFixture.cs b/src/NHibernate.Test/Async/TransformTests/AliasToBeanResultTransformerFixture.cs index 8edd1ee074c..c87b022cfba 100644 --- a/src/NHibernate.Test/Async/TransformTests/AliasToBeanResultTransformerFixture.cs +++ b/src/NHibernate.Test/Async/TransformTests/AliasToBeanResultTransformerFixture.cs @@ -8,6 +8,8 @@ //------------------------------------------------------------------------------ +using System.Collections.Generic; +using System.Linq; using System.Reflection; using NHibernate.Transform; using NHibernate.Util; @@ -263,6 +265,30 @@ public async Task SerializationAsync() await (AssertSerializationAsync()); } + enum TestEnum + { Value0, Value1 } + + class TestDto + { + private TestDto() + { } + + public TestDto(bool bogus) { } + + public string StringProp { get; set; } + public int IntProp { get; set; } + public int IntPropNull { get; set; } + public int? IntPropNullNullable { get; set; } + public TestEnum EnumProp { get; set; } + } + + class NoDefCtorDto + { + public NoDefCtorDto(bool bogus) + { + } + } + private async Task AssertCardinalityNameAndIdAsync(IResultTransformer transformer = null, CancellationToken cancellationToken = default(CancellationToken)) { using (var s = OpenSession()) diff --git a/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs b/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs index 8d78567f614..25f68c4b07d 100644 --- a/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs +++ b/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; +using System.Linq; using System.Reflection; using NHibernate.Transform; using NHibernate.Util; @@ -251,6 +253,70 @@ public void Serialization() AssertSerialization(); } + enum TestEnum + { Value0, Value1 } + + class TestDto + { + private TestDto() + { } + + public TestDto(bool bogus) { } + + public string StringProp { get; set; } + public int IntProp { get; set; } + public int IntPropNull { get; set; } + public int? IntPropNullNullable { get; set; } + public TestEnum EnumProp { get; set; } + } + + [Test] + public void TupleConversion() + { + var o = new TestDto(true) + { + IntProp = 1, + IntPropNull = 0, + StringProp = "hello", + IntPropNullNullable = null, + EnumProp = TestEnum.Value1, + }; + string nullMarker = "NULL"; + var testData = new Dictionary + { + {nameof(o.IntProp), o.IntProp}, + {nullMarker, decimal.MaxValue}, + {nameof(o.IntPropNull).ToLowerInvariant(), null}, + {string.Empty, new object()}, + {nameof(o.IntPropNullNullable).ToLowerInvariant(), null}, + {nameof(o.EnumProp), 1}, + {nameof(o.StringProp), o.StringProp}, + }; + var aliases = testData.Keys.Select(k => k == nullMarker ? null : k).ToArray(); + + var tuple = testData.Values.ToArray(); + + var actual = (TestDto) GetTransformer().TransformTuple(tuple, aliases); + Assert.That(actual.IntProp, Is.EqualTo(o.IntProp)); + Assert.That(actual.IntPropNull, Is.EqualTo(o.IntPropNull)); + Assert.That(actual.StringProp, Is.EqualTo(o.StringProp)); + Assert.That(actual.IntPropNullNullable, Is.EqualTo(o.IntPropNullNullable)); + Assert.That(actual.EnumProp, Is.EqualTo(o.EnumProp)); + } + + class NoDefCtorDto + { + public NoDefCtorDto(bool bogus) + { + } + } + + [Test] + public void ThrowsForClassWithoutDefaultCtor() + { + Assert.That(() => GetTransformer().TransformTuple(new object[0], new string[0]), Throws.ArgumentException); + } + private void AssertCardinalityNameAndId(IResultTransformer transformer = null) { using (var s = OpenSession()) From 4388c6dd6818d4abd854e1f06fcd2b7950fb1f49 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Tue, 26 Feb 2019 13:03:39 +0200 Subject: [PATCH 04/11] Added user friendly exception --- .../AliasToBeanResultTransformerFixture.cs | 9 ++++++ .../AliasToBeanResultTransformerFixture.cs | 29 ++++++++++++++++++- .../Transform/AliasToBeanResultTransformer.cs | 21 ++++++++++++-- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/NHibernate.Test/Async/TransformTests/AliasToBeanResultTransformerFixture.cs b/src/NHibernate.Test/Async/TransformTests/AliasToBeanResultTransformerFixture.cs index c87b022cfba..c8a863a7e90 100644 --- a/src/NHibernate.Test/Async/TransformTests/AliasToBeanResultTransformerFixture.cs +++ b/src/NHibernate.Test/Async/TransformTests/AliasToBeanResultTransformerFixture.cs @@ -282,6 +282,15 @@ public TestDto(bool bogus) { } public TestEnum EnumProp { get; set; } } + struct TestDtoAsStruct + { + public string StringProp { get; set; } + public int IntProp { get; set; } + public int IntPropNull { get; set; } + public int? IntPropNullNullable { get; set; } + public TestEnum EnumProp { get; set; } + } + class NoDefCtorDto { public NoDefCtorDto(bool bogus) diff --git a/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs b/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs index 25f68c4b07d..ae1779d5e5b 100644 --- a/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs +++ b/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs @@ -270,6 +270,15 @@ public TestDto(bool bogus) { } public TestEnum EnumProp { get; set; } } + struct TestDtoAsStruct + { + public string StringProp { get; set; } + public int IntProp { get; set; } + public int IntPropNull { get; set; } + public int? IntPropNullNullable { get; set; } + public TestEnum EnumProp { get; set; } + } + [Test] public void TupleConversion() { @@ -289,7 +298,7 @@ public void TupleConversion() {nameof(o.IntPropNull).ToLowerInvariant(), null}, {string.Empty, new object()}, {nameof(o.IntPropNullNullable).ToLowerInvariant(), null}, - {nameof(o.EnumProp), 1}, + {nameof(o.EnumProp), 1.5}, {nameof(o.StringProp), o.StringProp}, }; var aliases = testData.Keys.Select(k => k == nullMarker ? null : k).ToArray(); @@ -297,6 +306,7 @@ public void TupleConversion() var tuple = testData.Values.ToArray(); var actual = (TestDto) GetTransformer().TransformTuple(tuple, aliases); + var actualStruct = (TestDtoAsStruct) GetTransformer().TransformTuple(tuple, aliases); Assert.That(actual.IntProp, Is.EqualTo(o.IntProp)); Assert.That(actual.IntPropNull, Is.EqualTo(o.IntPropNull)); Assert.That(actual.StringProp, Is.EqualTo(o.StringProp)); @@ -304,6 +314,23 @@ public void TupleConversion() Assert.That(actual.EnumProp, Is.EqualTo(o.EnumProp)); } + [Test] + public void ThrowUserFriendlyException() + { + var o = new TestDto(true) { }; + + string nullMarker = "NULL"; + var testData = new Dictionary + { + {nameof(o.IntProp), "hello"}, + }; + var aliases = testData.Keys.Select(k => k == nullMarker ? null : k).ToArray(); + var tuple = testData.Values.ToArray(); + + var ex = Assert.Throws(() => GetTransformer().TransformTuple(tuple, aliases)); + Assert.That(ex, Has.Message.Contains(nameof(o.IntProp))); + } + class NoDefCtorDto { public NoDefCtorDto(bool bogus) diff --git a/src/NHibernate/Transform/AliasToBeanResultTransformer.cs b/src/NHibernate/Transform/AliasToBeanResultTransformer.cs index 44722557a8d..be5ed44e255 100644 --- a/src/NHibernate/Transform/AliasToBeanResultTransformer.cs +++ b/src/NHibernate/Transform/AliasToBeanResultTransformer.cs @@ -127,6 +127,8 @@ private Func CompileObjectIniter(string[] aliases) throw new ArgumentNullException("aliases"); } + Expression> getException = (name, obj) => new InvalidCastException($"Failed to set property '{name}' with value of type '{obj.GetType()}'"); + var bindings = new List(aliases.Length); var tupleParam = Expression.Parameter(typeof(object[]), "tuple"); for (int i = 0; i < aliases.Length; i++) @@ -137,7 +139,7 @@ private Func CompileObjectIniter(string[] aliases) var memberInfo = GetMemberInfo(alias); var valueExpr = Expression.ArrayAccess(tupleParam, Expression.Constant(i)); - bindings.Add(Expression.Bind(memberInfo, GetTyped(memberInfo, valueExpr))); + bindings.Add(Expression.Bind(memberInfo, GetTyped(memberInfo, valueExpr, getException))); } Expression initExpr = Expression.MemberInit(GetNewExpression(ResultClass), bindings); @@ -147,16 +149,29 @@ private Func CompileObjectIniter(string[] aliases) return (Func) Expression.Lambda(initExpr, tupleParam).Compile(); } - private static Expression GetTyped(MemberInfo memberInfo, Expression expr) + private static Expression GetTyped(MemberInfo memberInfo, Expression expr, Expression> getEx) { var type = GetMemberType(memberInfo); if (type == typeof(object)) return expr; + + var originalValue = expr; if (!type.IsClass) { expr = Expression.Coalesce(expr, Expression.Default(type)); } - return Expression.Convert(expr, type); + expr = Expression.Convert(expr, type); + + var tryCatch = + Expression.TryCatch( + expr, + Expression.Catch( + typeof(InvalidCastException), + Expression.Throw(Expression.Invoke(getEx, Expression.Constant(memberInfo.Name), originalValue), type) + )); + if (type.IsClass) + return expr; + return Expression.Block(type, tryCatch); } private static System.Type GetMemberType(MemberInfo memberInfo) From 3c190c3970d1c5631749099146067d840ab12079 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Tue, 26 Feb 2019 13:27:12 +0200 Subject: [PATCH 05/11] Refactored --- .../Transform/AliasToBeanResultTransformer.cs | 30 ++++--------------- 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/src/NHibernate/Transform/AliasToBeanResultTransformer.cs b/src/NHibernate/Transform/AliasToBeanResultTransformer.cs index be5ed44e255..77a1925aade 100644 --- a/src/NHibernate/Transform/AliasToBeanResultTransformer.cs +++ b/src/NHibernate/Transform/AliasToBeanResultTransformer.cs @@ -44,7 +44,7 @@ public class AliasToBeanResultTransformer : AliasedTupleSubsetResultTransformer, private readonly Dictionary> _propertiesByNameCaseInsensitive; [NonSerialized] - Func _objectIniter; + Func _transformer; public System.Type ResultClass => _resultClass; @@ -69,8 +69,8 @@ public override bool IsTransformedValueATupleElement(String[] aliases, int tuple public override object TransformTuple(object[] tuple, string[] aliases) { - var initer = _objectIniter ?? (_objectIniter = CompileObjectIniter(aliases)); - return initer(tuple); + var transformer = _transformer ?? (_transformer = CreateTransformerDelegate(aliases)); + return transformer(tuple); } public override IList TransformList(IList collection) @@ -120,14 +120,14 @@ private void CheckMember(NamedMember member, string alias) where T : Membe $"{string.Join(", ", member.AmbiguousMembers.Select(m => m.Name))}"); } - private Func CompileObjectIniter(string[] aliases) + protected Func CreateTransformerDelegate(string[] aliases) { if (aliases == null) { throw new ArgumentNullException("aliases"); } - Expression> getException = (name, obj) => new InvalidCastException($"Failed to set property '{name}' with value of type '{obj.GetType()}'"); + Expression> getException = (name, obj) => new InvalidCastException($"Failed to set property/field '{name}' with value of type '{obj.GetType()}'"); var bindings = new List(aliases.Length); var tupleParam = Expression.Parameter(typeof(object[]), "tuple"); @@ -142,7 +142,7 @@ private Func CompileObjectIniter(string[] aliases) bindings.Add(Expression.Bind(memberInfo, GetTyped(memberInfo, valueExpr, getException))); } - Expression initExpr = Expression.MemberInit(GetNewExpression(ResultClass), bindings); + Expression initExpr = Expression.MemberInit(Expression.New(_resultClass), bindings); if (!ResultClass.IsClass) initExpr = Expression.Convert(initExpr, typeof(object)); @@ -185,24 +185,6 @@ private static System.Type GetMemberType(MemberInfo memberInfo) throw new NotSupportedException($"Member type {memberInfo} is not supported"); } - private static NewExpression GetNewExpression(System.Type resultClass) - { - if (!resultClass.IsClass) - return Expression.New(resultClass); - - const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; - var beanConstructor = resultClass.GetConstructor(bindingFlags, null, System.Type.EmptyTypes, null); - - if (beanConstructor == null) - { - throw new ArgumentException( - "The target class of a AliasToBeanResultTransformer need a parameter-less constructor", - nameof(resultClass)); - } - - return Expression.New(beanConstructor); - } - private static void FetchFieldsAndProperties(System.Type resultClass, List> fields, List> properties) { const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly; From 25578ae2cdb1c670959aef91f7399fa93557780a Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Tue, 26 Feb 2019 13:30:41 +0200 Subject: [PATCH 06/11] fixup! Added user friendly exception --- .../TransformTests/AliasToBeanResultTransformerFixture.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs b/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs index ae1779d5e5b..cf85155ad5a 100644 --- a/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs +++ b/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs @@ -298,7 +298,7 @@ public void TupleConversion() {nameof(o.IntPropNull).ToLowerInvariant(), null}, {string.Empty, new object()}, {nameof(o.IntPropNullNullable).ToLowerInvariant(), null}, - {nameof(o.EnumProp), 1.5}, + {nameof(o.EnumProp), 1}, {nameof(o.StringProp), o.StringProp}, }; var aliases = testData.Keys.Select(k => k == nullMarker ? null : k).ToArray(); From d14af7e8294657fdc34ae49871ba043f324ceada Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Tue, 26 Feb 2019 13:40:54 +0200 Subject: [PATCH 07/11] fixup! Added user friendly message --- src/NHibernate/Transform/AliasToBeanResultTransformer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NHibernate/Transform/AliasToBeanResultTransformer.cs b/src/NHibernate/Transform/AliasToBeanResultTransformer.cs index 77a1925aade..c5ee75a5269 100644 --- a/src/NHibernate/Transform/AliasToBeanResultTransformer.cs +++ b/src/NHibernate/Transform/AliasToBeanResultTransformer.cs @@ -127,7 +127,7 @@ protected Func CreateTransformerDelegate(string[] aliases) throw new ArgumentNullException("aliases"); } - Expression> getException = (name, obj) => new InvalidCastException($"Failed to set property/field '{name}' with value of type '{obj.GetType()}'"); + Expression> getException = (name, obj) => new InvalidCastException($"Failed to init member '{name}' with value of type '{obj.GetType()}'"); var bindings = new List(aliases.Length); var tupleParam = Expression.Parameter(typeof(object[]), "tuple"); @@ -167,7 +167,7 @@ private static Expression GetTyped(MemberInfo memberInfo, Expression expr, Expre expr, Expression.Catch( typeof(InvalidCastException), - Expression.Throw(Expression.Invoke(getEx, Expression.Constant(memberInfo.Name), originalValue), type) + Expression.Throw(Expression.Invoke(getEx, Expression.Constant(memberInfo.ToString()), originalValue), type) )); if (type.IsClass) return expr; From 4759da0681773efa9c08757bec1e610d7d64e449 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Tue, 26 Feb 2019 18:17:07 +0200 Subject: [PATCH 08/11] remove for now nice exception for struct DTO on netfx --- .../AliasToBeanResultTransformerFixture.cs | 5 +++++ .../Transform/AliasToBeanResultTransformer.cs | 14 +++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs b/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs index cf85155ad5a..18b20639806 100644 --- a/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs +++ b/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs @@ -329,6 +329,11 @@ public void ThrowUserFriendlyException() var ex = Assert.Throws(() => GetTransformer().TransformTuple(tuple, aliases)); Assert.That(ex, Has.Message.Contains(nameof(o.IntProp))); + var ex2 = Assert.Throws(() => GetTransformer().TransformTuple(tuple, aliases)); + +#if NETSTANDARD2_0 + Assert.That(ex2, Has.Message.Contains(nameof(o.IntProp))); +#endif } class NoDefCtorDto diff --git a/src/NHibernate/Transform/AliasToBeanResultTransformer.cs b/src/NHibernate/Transform/AliasToBeanResultTransformer.cs index c5ee75a5269..7123130de96 100644 --- a/src/NHibernate/Transform/AliasToBeanResultTransformer.cs +++ b/src/NHibernate/Transform/AliasToBeanResultTransformer.cs @@ -162,16 +162,20 @@ private static Expression GetTyped(MemberInfo memberInfo, Expression expr, Expre } expr = Expression.Convert(expr, type); - var tryCatch = - Expression.TryCatch( +#if NETFX + //TODO: find an universal way for check + //On .net461 throws if DTO is struct: TryExpression is not supported as a child expression when accessing a member on type '[TYPE]' because it is a value type. + if (!memberInfo.DeclaringType.IsClass) + return expr; +#endif + + expr = Expression.TryCatch( expr, Expression.Catch( typeof(InvalidCastException), Expression.Throw(Expression.Invoke(getEx, Expression.Constant(memberInfo.ToString()), originalValue), type) )); - if (type.IsClass) - return expr; - return Expression.Block(type, tryCatch); + return expr; } private static System.Type GetMemberType(MemberInfo memberInfo) From 487885e93e2fec81c6909ef3cc1f8cc2d0e64e9a Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Thu, 28 Feb 2019 10:15:19 +0200 Subject: [PATCH 09/11] Wrap assignments in variables for DTO structs on netfx --- .../AliasToBeanResultTransformerFixture.cs | 3 -- .../Transform/AliasToBeanResultTransformer.cs | 42 ++++++++++++------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs b/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs index 18b20639806..79361bceb23 100644 --- a/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs +++ b/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs @@ -330,10 +330,7 @@ public void ThrowUserFriendlyException() var ex = Assert.Throws(() => GetTransformer().TransformTuple(tuple, aliases)); Assert.That(ex, Has.Message.Contains(nameof(o.IntProp))); var ex2 = Assert.Throws(() => GetTransformer().TransformTuple(tuple, aliases)); - -#if NETSTANDARD2_0 Assert.That(ex2, Has.Message.Contains(nameof(o.IntProp))); -#endif } class NoDefCtorDto diff --git a/src/NHibernate/Transform/AliasToBeanResultTransformer.cs b/src/NHibernate/Transform/AliasToBeanResultTransformer.cs index 7123130de96..dc9969d664e 100644 --- a/src/NHibernate/Transform/AliasToBeanResultTransformer.cs +++ b/src/NHibernate/Transform/AliasToBeanResultTransformer.cs @@ -128,9 +128,12 @@ protected Func CreateTransformerDelegate(string[] aliases) } Expression> getException = (name, obj) => new InvalidCastException($"Failed to init member '{name}' with value of type '{obj.GetType()}'"); - + bool wrapInVariables = ShouldWrapInVariables(); var bindings = new List(aliases.Length); var tupleParam = Expression.Parameter(typeof(object[]), "tuple"); + var variableAndAssigmentDic = wrapInVariables + ? new Dictionary(aliases.Length) + : null; for (int i = 0; i < aliases.Length; i++) { string alias = aliases[i]; @@ -139,16 +142,36 @@ protected Func CreateTransformerDelegate(string[] aliases) var memberInfo = GetMemberInfo(alias); var valueExpr = Expression.ArrayAccess(tupleParam, Expression.Constant(i)); - bindings.Add(Expression.Bind(memberInfo, GetTyped(memberInfo, valueExpr, getException))); + var valueToAssign = GetTyped(memberInfo, valueExpr, getException); + if (wrapInVariables) + { + var variable = Expression.Variable(valueToAssign.Type); + variableAndAssigmentDic[variable] = Expression.Assign(variable, valueToAssign); + valueToAssign = variable; + } + bindings.Add(Expression.Bind(memberInfo, valueToAssign)); } - Expression initExpr = Expression.MemberInit(Expression.New(_resultClass), bindings); if (!ResultClass.IsClass) initExpr = Expression.Convert(initExpr, typeof(object)); + if (wrapInVariables) + { + initExpr = Expression.Block(variableAndAssigmentDic.Keys, variableAndAssigmentDic.Values.Concat(new[] { initExpr })); + } return (Func) Expression.Lambda(initExpr, tupleParam).Compile(); } + private bool ShouldWrapInVariables() + { +#if NETFX + //On .net461 throws if DTO is struct: TryExpression is not supported as a child expression when accessing a member on type '[TYPE]' because it is a value type. + if (!_resultClass.IsClass) + return true; +#endif + return false; + } + private static Expression GetTyped(MemberInfo memberInfo, Expression expr, Expression> getEx) { var type = GetMemberType(memberInfo); @@ -160,22 +183,13 @@ private static Expression GetTyped(MemberInfo memberInfo, Expression expr, Expre { expr = Expression.Coalesce(expr, Expression.Default(type)); } - expr = Expression.Convert(expr, type); - -#if NETFX - //TODO: find an universal way for check - //On .net461 throws if DTO is struct: TryExpression is not supported as a child expression when accessing a member on type '[TYPE]' because it is a value type. - if (!memberInfo.DeclaringType.IsClass) - return expr; -#endif - expr = Expression.TryCatch( - expr, + return Expression.TryCatch( + Expression.Convert(expr, type), Expression.Catch( typeof(InvalidCastException), Expression.Throw(Expression.Invoke(getEx, Expression.Constant(memberInfo.ToString()), originalValue), type) )); - return expr; } private static System.Type GetMemberType(MemberInfo memberInfo) From abed4e53a6db572531b5a63893290534bbcdff96 Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Wed, 13 Mar 2019 22:06:58 +1300 Subject: [PATCH 10/11] Inline GetTransformer --- .../AliasToBeanResultTransformerFixture.cs | 15 ++++------- .../AliasToBeanResultTransformerFixture.cs | 25 ++++++++----------- 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/src/NHibernate.Test/Async/TransformTests/AliasToBeanResultTransformerFixture.cs b/src/NHibernate.Test/Async/TransformTests/AliasToBeanResultTransformerFixture.cs index c8a863a7e90..665d22cd924 100644 --- a/src/NHibernate.Test/Async/TransformTests/AliasToBeanResultTransformerFixture.cs +++ b/src/NHibernate.Test/Async/TransformTests/AliasToBeanResultTransformerFixture.cs @@ -226,7 +226,7 @@ public async Task WorksWithManyCandidatesAsync() { using (var s = OpenSession()) { - var transformer = GetTransformer(); + var transformer = Transformers.AliasToBean(); var l = await (s.CreateSQLQuery("select id as ID, Name as NamE from Simple") .SetResultTransformer(transformer) .ListAsync()); @@ -245,7 +245,7 @@ public void ToPropertiesInsensitivelyDuplicated_WithoutAnyProjectionsAsync() { using (var s = OpenSession()) { - var transformer = GetTransformer(); + var transformer = Transformers.AliasToBean(); Assert.ThrowsAsync(() => { return s.CreateSQLQuery("select * from Simple") @@ -302,7 +302,7 @@ public NoDefCtorDto(bool bogus) { using (var s = OpenSession()) { - transformer = transformer ?? GetTransformer(); + transformer = transformer ?? Transformers.AliasToBean(); var l = await (s.CreateSQLQuery("select * from Simple") .SetResultTransformer(transformer) .ListAsync(cancellationToken)); @@ -319,7 +319,7 @@ public NoDefCtorDto(bool bogus) { using (var s = OpenSession()) { - var transformer = GetTransformer(); + var transformer = Transformers.AliasToBean(); var l = await (s.CreateSQLQuery(queryString) .SetResultTransformer(transformer) .ListAsync(cancellationToken)); @@ -335,7 +335,7 @@ public NoDefCtorDto(bool bogus) { try { - var transformer = GetTransformer(); + var transformer = Transformers.AliasToBean(); var bytes = SerializationHelper.Serialize(transformer); transformer = (IResultTransformer) SerializationHelper.Deserialize(bytes); return AssertCardinalityNameAndIdAsync(transformer: transformer, cancellationToken: cancellationToken); @@ -345,10 +345,5 @@ public NoDefCtorDto(bool bogus) return Task.FromException(ex); } } - - protected IResultTransformer GetTransformer() - { - return Transformers.AliasToBean(); - } } } diff --git a/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs b/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs index 79361bceb23..2e9c0998346 100644 --- a/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs +++ b/src/NHibernate.Test/TransformTests/AliasToBeanResultTransformerFixture.cs @@ -214,7 +214,7 @@ public void WorksWithManyCandidates() { using (var s = OpenSession()) { - var transformer = GetTransformer(); + var transformer = Transformers.AliasToBean(); var l = s.CreateSQLQuery("select id as ID, Name as NamE from Simple") .SetResultTransformer(transformer) .List(); @@ -233,7 +233,7 @@ public void ToPropertiesInsensitivelyDuplicated_WithoutAnyProjections() { using (var s = OpenSession()) { - var transformer = GetTransformer(); + var transformer = Transformers.AliasToBean(); Assert.Throws(() => { s.CreateSQLQuery("select * from Simple") @@ -305,8 +305,8 @@ public void TupleConversion() var tuple = testData.Values.ToArray(); - var actual = (TestDto) GetTransformer().TransformTuple(tuple, aliases); - var actualStruct = (TestDtoAsStruct) GetTransformer().TransformTuple(tuple, aliases); + var actual = (TestDto) Transformers.AliasToBean().TransformTuple(tuple, aliases); + var actualStruct = (TestDtoAsStruct) Transformers.AliasToBean().TransformTuple(tuple, aliases); Assert.That(actual.IntProp, Is.EqualTo(o.IntProp)); Assert.That(actual.IntPropNull, Is.EqualTo(o.IntPropNull)); Assert.That(actual.StringProp, Is.EqualTo(o.StringProp)); @@ -327,9 +327,9 @@ public void ThrowUserFriendlyException() var aliases = testData.Keys.Select(k => k == nullMarker ? null : k).ToArray(); var tuple = testData.Values.ToArray(); - var ex = Assert.Throws(() => GetTransformer().TransformTuple(tuple, aliases)); + var ex = Assert.Throws(() => Transformers.AliasToBean().TransformTuple(tuple, aliases)); Assert.That(ex, Has.Message.Contains(nameof(o.IntProp))); - var ex2 = Assert.Throws(() => GetTransformer().TransformTuple(tuple, aliases)); + var ex2 = Assert.Throws(() => Transformers.AliasToBean().TransformTuple(tuple, aliases)); Assert.That(ex2, Has.Message.Contains(nameof(o.IntProp))); } @@ -343,14 +343,14 @@ public NoDefCtorDto(bool bogus) [Test] public void ThrowsForClassWithoutDefaultCtor() { - Assert.That(() => GetTransformer().TransformTuple(new object[0], new string[0]), Throws.ArgumentException); + Assert.That(() => Transformers.AliasToBean().TransformTuple(new object[0], new string[0]), Throws.ArgumentException); } private void AssertCardinalityNameAndId(IResultTransformer transformer = null) { using (var s = OpenSession()) { - transformer = transformer ?? GetTransformer(); + transformer = transformer ?? Transformers.AliasToBean(); var l = s.CreateSQLQuery("select * from Simple") .SetResultTransformer(transformer) .List(); @@ -367,7 +367,7 @@ private void AssertCardinalityAndSomething(string queryString = "select s.Nam { using (var s = OpenSession()) { - var transformer = GetTransformer(); + var transformer = Transformers.AliasToBean(); var l = s.CreateSQLQuery(queryString) .SetResultTransformer(transformer) .List(); @@ -381,15 +381,10 @@ private void AssertCardinalityAndSomething(string queryString = "select s.Nam private void AssertSerialization() { - var transformer = GetTransformer(); + var transformer = Transformers.AliasToBean(); var bytes = SerializationHelper.Serialize(transformer); transformer = (IResultTransformer) SerializationHelper.Deserialize(bytes); AssertCardinalityNameAndId(transformer: transformer); } - - protected IResultTransformer GetTransformer() - { - return Transformers.AliasToBean(); - } } } From b6478c4de90706a23e0ebb4fd1a48ab699e3299b Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Wed, 13 Mar 2019 22:07:19 +1300 Subject: [PATCH 11/11] Remove unused params from TryGetMemberInfo --- .../Transform/AliasToBeanResultTransformer.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/NHibernate/Transform/AliasToBeanResultTransformer.cs b/src/NHibernate/Transform/AliasToBeanResultTransformer.cs index dc9969d664e..ae0ad868f46 100644 --- a/src/NHibernate/Transform/AliasToBeanResultTransformer.cs +++ b/src/NHibernate/Transform/AliasToBeanResultTransformer.cs @@ -89,16 +89,15 @@ protected MemberInfo GetMemberInfo(string alias) ?? throw new PropertyNotFoundException(_resultClass, alias, "setter"); } - private TMemberInfo TryGetMemberInfo(string alias, params Dictionary>[] propertiesMaps) where TMemberInfo: MemberInfo + private TMemberInfo TryGetMemberInfo(string alias, Dictionary> propertiesMap) + where TMemberInfo: MemberInfo { - foreach (var propertiesMap in propertiesMaps) + if (propertiesMap.TryGetValue(alias, out var property)) { - if (propertiesMap.TryGetValue(alias, out var property)) - { - CheckMember(property, alias); - return property.Member; - } + CheckMember(property, alias); + return property.Member; } + return null; } @@ -109,7 +108,7 @@ private void CheckMember(NamedMember member, string alias) where T : Membe if (member.AmbiguousMembers == null || member.AmbiguousMembers.Length < 2) { - // Should never happen, check NamedMember instanciations. + // Should never happen, check NamedMember instantiations. throw new InvalidOperationException( $"{nameof(NamedMember.Member)} missing and {nameof(NamedMember.AmbiguousMembers)} invalid."); }