diff --git a/ChangeLog/7.1.3_dev.txt b/ChangeLog/7.1.3_dev.txt index 1a6f278e90..f2e3146bb8 100644 --- a/ChangeLog/7.1.3_dev.txt +++ b/ChangeLog/7.1.3_dev.txt @@ -1,3 +1,4 @@ [main] Addressed race condition issue with TranslatorState.NonVisitableExpressions +[main] Improved working with nullable enum constants in queries [postgresql] Improved database structucture extraction [postgresql] Addressed certain cases of decimal results of aggregate operations being unable to fit to .NET decimal \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests.Core/Linq/EnumRewriterTest.cs b/Orm/Xtensive.Orm.Tests.Core/Linq/EnumRewriterTest.cs new file mode 100644 index 0000000000..a7897a59d2 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests.Core/Linq/EnumRewriterTest.cs @@ -0,0 +1,186 @@ +using System; +using System.Linq.Expressions; +using System.Collections.Generic; +using System.Text; +using Xtensive.Reflection; +using Xtensive.Orm.Linq.Expressions.Visitors; +using NUnit.Framework; +using System.Linq; + +namespace Xtensive.Orm.Tests.Core.Linq +{ + public class EnumRewriterTest + { + private enum ByteBasedEnum : byte + { + Value1 = 1, Value2, Value3 + } + + private enum SByteBasedEnum : sbyte + { + Value1 = 1, Value2, Value3 + } + + private enum ShortBasedEnum : short + { + Value1 = 1, Value2, Value3 + } + + private enum UShortBasedEnum : ushort + { + Value1 = 1, Value2, Value3 + } + + private enum IntBasedEnum : int + { + Value1 = 1, Value2, Value3 + } + + private enum UIntBasedEnum : uint + { + Value1 = 1, Value2, Value3 + } + + private enum LongBasedEnum : long + { + Value1 = 1, Value2, Value3 + } + + private enum ULongBasedEnum : ulong + { + Value1 = 1, Value2, Value3 + } + + + private Expression[] Expressions; + + [OneTimeSetUp] + public void TestFixtureSetUp() + { + Expressions = new[] { + // non-enum constants + Expression.Constant(1, typeof(int)), + Expression.Constant(2, typeof(int?)), + Expression.Constant(null, typeof(int?)), + + //short enums + Expression.Constant(ShortBasedEnum.Value1, typeof(ShortBasedEnum)), + Expression.Constant(ShortBasedEnum.Value1, typeof(ShortBasedEnum)), + Expression.Constant(ShortBasedEnum.Value1, typeof(ShortBasedEnum)), + Expression.Constant(ShortBasedEnum.Value1, typeof(ShortBasedEnum)), + Expression.Constant(IntBasedEnum.Value1, typeof(IntBasedEnum)), + Expression.Constant(LongBasedEnum.Value1, typeof(LongBasedEnum)), + + Expression.Constant(ShortBasedEnum.Value2, typeof(ShortBasedEnum?)), + Expression.Constant(IntBasedEnum.Value2, typeof(IntBasedEnum?)), + Expression.Constant(LongBasedEnum.Value2, typeof(LongBasedEnum?)), + + Expression.Constant(null, typeof(ShortBasedEnum?)), + Expression.Constant(null, typeof(IntBasedEnum?)), + Expression.Constant(null, typeof(LongBasedEnum?)), + }; + } + + [Test] + public void NonEnumValuesTest() + { + foreach (var exp in Expressions.Take(3)) { + var rewrited = EnumRewriter.Rewrite(exp); + Assert.That(rewrited, Is.EqualTo(exp)); + } + } + + [Test] + public void NonNullableEnumsTest() + { + foreach (var exp in Expressions.Skip(3).Take(3)) { + var rewrited = EnumRewriter.Rewrite(exp); + var expType = exp.Type; + var enumType = expType.StripNullable(); + Assert.That(rewrited, Is.InstanceOf()); + var convert = rewrited as UnaryExpression; + Assert.That(convert.NodeType, Is.EqualTo(ExpressionType.Convert)); + Assert.That(convert.Type, Is.EqualTo(expType)); + var operand = convert.Operand; + Assert.That(operand, Is.InstanceOf()); + var constant = operand as ConstantExpression; + Assert.That(constant.Type, Is.Not.EqualTo(enumType)); + Assert.That(constant.Type, Is.EqualTo(Enum.GetUnderlyingType(enumType))); + } + } + + [Test] + public void NullableEnumsTest() + { + foreach (var exp in Expressions.Skip(6).Take(3)) { + var rewrited = EnumRewriter.Rewrite(exp); + var expType = exp.Type; + var enumType = expType.StripNullable(); + + Assert.That(rewrited, Is.InstanceOf()); + var convert = rewrited as UnaryExpression; + Assert.That(convert.NodeType, Is.EqualTo(ExpressionType.Convert)); + Assert.That(convert.Type, Is.EqualTo(expType)); + var operand = convert.Operand; + Assert.That(operand, Is.InstanceOf()); + var constant = operand as ConstantExpression; + Assert.That(constant.Type, Is.Not.EqualTo(enumType)); + Assert.That(constant.Type, Is.EqualTo(Enum.GetUnderlyingType(enumType))); + Assert.That(constant.Value, Is.GreaterThan(1)); + + } + } + + [Test] + public void NullsAsNullableEnumsTest() + { + foreach (var exp in Expressions.Skip(9).Take(3)) { + + var rewrited = EnumRewriter.Rewrite(exp); + var expType = exp.Type; + var enumType = expType.StripNullable(); + + Assert.That(rewrited, Is.InstanceOf()); + var convert = rewrited as UnaryExpression; + Assert.That(convert.NodeType, Is.EqualTo(ExpressionType.Convert)); + Assert.That(convert.Type, Is.EqualTo(expType)); + var operand = convert.Operand; + Assert.That(operand, Is.InstanceOf()); + var constant = operand as ConstantExpression; + Assert.That(constant.Type, Is.Not.EqualTo(enumType)); + Assert.That(constant.Type, Is.EqualTo(typeof(object))); + Assert.That(constant.Value, Is.Null); + } + } + + //[Test] + //public void ComplexTest() + //{ + // foreach (var exp in Expressions) { + // var rewrited = EnumRewriter.Rewrite(exp); + // var expType = exp.Type; + // if (exp is ConstantExpression testExp && expType.StripNullable().IsEnum) { + // var isNullable = expType.IsNullable(); + // var enumType = expType.StripNullable(); + // if (isNullable) { + + // } + // else { + // Assert.That(rewrited, Is.InstanceOf()); + // var convert = rewrited as UnaryExpression; + // Assert.That(convert.NodeType, Is.EqualTo(ExpressionType.Convert)); + // Assert.That(convert.Type, Is.EqualTo(expType)); + // var operand = convert.Operand; + // Assert.That(operand, Is.InstanceOf()); + // var constant = operand as ConstantExpression; + // Assert.That(constant.Type, Is.Not.EqualTo(enumType)); + // Assert.That(constant.Type, Is.EqualTo(Enum.GetUnderlyingType(enumType))); + // } + // } + // else { + // Assert.That(rewrited, Is.EqualTo(exp)); + // } + // } + //} + } +} diff --git a/Orm/Xtensive.Orm.Tests/Linq/GroupByTest.cs b/Orm/Xtensive.Orm.Tests/Linq/GroupByTest.cs index 11e11d087a..3e8a24bb66 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/GroupByTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/GroupByTest.cs @@ -1051,6 +1051,20 @@ public void GroupByBoolExpressionComplex() Assert.AreEqual(falseResult, result.Single(i => !i.Value).Count); Assert.AreEqual(trueResult, result.Single(i => i.Value).Count); } + + [Test] + public void GroupByEnumTernaryWithNonNullConstTest() + { + var query = Session.Query.All() + .GroupBy(c => c.Total < 0 ? (InvoiceStatus?) InvoiceStatus.Completed : c.Status).ToArray(); + } + + [Test] + public void GroupByEnumTernaryWithNullConstTest() + { + var query = Session.Query.All() + .GroupBy(c => c.Total < 0 ? (InvoiceStatus?) null : c.Status).ToArray(); + } private void DumpGrouping(IQueryable> result) { diff --git a/Orm/Xtensive.Orm.Tests/Linq/SelectDtoTest.cs b/Orm/Xtensive.Orm.Tests/Linq/SelectDtoTest.cs index b4fa754263..ecd8d7dbb0 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/SelectDtoTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/SelectDtoTest.cs @@ -25,8 +25,7 @@ public enum BudgetType } [HierarchyRoot] - public class Manager - : Entity + public class Manager : Entity { [Field, Key] public int Id { get; private set; } @@ -36,11 +35,15 @@ public class Manager [Field,Association(PairTo = "Manager")] public EntitySet Persons { get; private set; } + + public Manager(Session session) + : base(session) + { + } } [HierarchyRoot] - public class Person - : Entity + public class Person : Entity { [Field, Key] public int Id { get; private set; } @@ -56,6 +59,11 @@ public class Person [Field] public BudgetType? BudgetType { get; set; } + + public Person(Session session) + : base(session) + { + } } public class ManagerDto @@ -90,7 +98,7 @@ public void MainTest() { using (var session = Domain.OpenSession()) using (var t = session.OpenTransaction()) { - var alex = new Person() { Name = "Alex" }; + var alex = new Person(session) { Name = "Alex" }; var query = session.Query.All() .Select(p => new PersonDto {Name = p.Name}) .Where(personDto => personDto.Name == "Alex"); @@ -105,8 +113,8 @@ public void WhereTest() { using (var session = Domain.OpenSession()) using (var t = session.OpenTransaction()) { - var person5 = new Person() {Name = "John", Tag = 5, BudgetType = BudgetType.Regional}; - var personEmpty = new Person() {Name = "John"}; + var person5 = new Person(session) {Name = "John", Tag = 5, BudgetType = BudgetType.Regional}; + var personEmpty = new Person(session) {Name = "John"}; var count = session.Query.All() .Select(p => new PersonDto() { Id = p.Id, Name = p.Name, Tag = p.Tag, BudgetType = p.BudgetType}) @@ -118,24 +126,58 @@ public void WhereTest() [Test] public void EnumTest() { + BudgetType budgetType = BudgetType.Regional; + BudgetType budgetTypeNotNullable = BudgetType.State; + BudgetType? budgetTypeNullable = BudgetType.Default; + BudgetType? nullBudgetType = null; + using (var session = Domain.OpenSession()) using (var t = session.OpenTransaction()) { - BudgetType budgetType = BudgetType.Regional; - BudgetType budgetTypeNotNullable = BudgetType.Regional; - BudgetType? budgetTypeNullable = BudgetType.Regional; + _ = new Person(session) { Name = "A", BudgetType = BudgetType.Default }; + _ = new Person(session) { Name = "B", BudgetType = BudgetType.Default }; + _ = new Person(session) { Name = "C", BudgetType = BudgetType.Regional }; + _ = new Person(session) { Name = "D", BudgetType = BudgetType.State }; + _ = new Person(session) { Name = "E" }; + _ = new Person(session) { Name = "F" }; + _ = new Person(session) { Name = null }; + _ = new Person(session) { Name = null }; + + session.SaveChanges(); + Expression> filterExpression = p => p.BudgetType == budgetType; + var regionalBudgetPeople = session.Query.All().Where(filterExpression).ToList(); + Assert.That(regionalBudgetPeople.Count, Is.EqualTo(1)); + Assert.That(regionalBudgetPeople[0].Name, Is.EqualTo("C")); + + Expression> filterNotNullableExpression = p => p.BudgetType == budgetTypeNotNullable; + var stateBudgetPeople = session.Query.All().Where(filterNotNullableExpression).ToList(); + Assert.That(stateBudgetPeople.Count, Is.EqualTo(1)); + Assert.That(stateBudgetPeople[0].Name, Is.EqualTo("D")); + Expression> filterNullableExpression = p => p.BudgetType == budgetTypeNullable; - Expression> filterNotNullableExpression = p => budgetTypeNotNullable == budgetType; + var defaultBudgetPeople = session.Query.All().Where(filterNullableExpression).ToList(); + Assert.That(defaultBudgetPeople.Count, Is.EqualTo(2)); + Assert.That(defaultBudgetPeople[0].Name, Is.EqualTo("A").Or.EqualTo("B")); + Assert.That(defaultBudgetPeople[1].Name, Is.EqualTo("A").Or.EqualTo("B")); + + Expression> filterNullExpression = p => p.BudgetType == nullBudgetType; + var undefinedBudgetPeople = session.Query.All().Where(filterNullExpression).ToList(); + Assert.That(undefinedBudgetPeople.Count, Is.EqualTo(4)); + foreach (var person in undefinedBudgetPeople) { + Assert.That(undefinedBudgetPeople[0].Name, Is.EqualTo("E").Or.EqualTo("F").Or.EqualTo(null)); + } + Expression> propertyExpression = p => p.BudgetType; var valueExpression = Expression.Convert(Expression.Constant(budgetType), typeof(BudgetType?)); var body = Expression.Convert(propertyExpression.Body, typeof(BudgetType?)); - Expression> customFilterExpression = Expression.Lambda>( + var customFilterExpression = Expression.Lambda>( Expression.Equal(body, valueExpression), propertyExpression.Parameters); - var persons = session.Query.All().Where(filterExpression).ToList(); - var customPersons = session.Query.All().Where(customFilterExpression).ToList(); + var customFilterExpressionResults = session.Query.All().Where(customFilterExpression).ToList(); + Assert.That(customFilterExpressionResults.OrderBy(p => p.Id).SequenceEqual(regionalBudgetPeople.OrderBy(p => p.Id)), Is.True); + var func = customFilterExpression.Compile(); - func(new Person() {BudgetType = BudgetType.Regional}); + Assert.That(func(new Person(session) {BudgetType = BudgetType.Regional}),Is.True); } } @@ -144,14 +186,14 @@ public void SelectNullableTest() { using (var session = Domain.OpenSession()) using (var t = session.OpenTransaction()) { - var manager1 = new Manager() {Name = "M0"}; - var manager2 = new Manager() {Name = "M0"}; - new Person() { Name = "A", Manager = manager1}; - new Person() { Name = "B", Manager = manager1}; - new Person() { Name = "C", Manager = manager2}; - new Person() { Name = "D", Manager = manager2}; - new Person() { Name = "E" }; - new Person() { Name = "F" }; + var manager1 = new Manager(session) {Name = "M0"}; + var manager2 = new Manager(session) {Name = "M0"}; + _ = new Person(session) { Name = "A", Manager = manager1}; + _ = new Person(session) { Name = "B", Manager = manager1}; + _ = new Person(session) { Name = "C", Manager = manager2}; + _ = new Person(session) { Name = "D", Manager = manager2}; + _ = new Person(session) { Name = "E" }; + _ = new Person(session) { Name = "F" }; var query = session.Query.All() .Select(p => new PersonDto { @@ -169,12 +211,12 @@ public void GroupByTest() Require.AllFeaturesSupported(ProviderFeatures.ScalarSubqueries); using (var session = Domain.OpenSession()) using (var t = session.OpenTransaction()) { - new Person() {Name = "A", BudgetType = BudgetType.Default}; - new Person() {Name = "B", BudgetType = BudgetType.Default}; - new Person() {Name = "C", BudgetType = BudgetType.Regional}; - new Person() {Name = "D", BudgetType = BudgetType.State}; - new Person() {Name = "E"}; - new Person() {Name = "F"}; + _ = new Person(session) {Name = "A", BudgetType = BudgetType.Default}; + _ = new Person(session) {Name = "B", BudgetType = BudgetType.Default}; + _ = new Person(session) {Name = "C", BudgetType = BudgetType.Regional}; + _ = new Person(session) {Name = "D", BudgetType = BudgetType.State}; + _ = new Person(session) {Name = "E"}; + _ = new Person(session) {Name = "F"}; var types = session.Query.All() .Select(p => p.BudgetType) @@ -203,24 +245,33 @@ public void MethodProjectionTest() { using (var session = Domain.OpenSession()) using (var t = session.OpenTransaction()) { - new Person() { Name = "A", BudgetType = BudgetType.Default }; - new Person() { Name = "B", BudgetType = BudgetType.Default }; - new Person() { Name = "C", BudgetType = BudgetType.Regional }; - new Person() { Name = "D", BudgetType = BudgetType.State }; - new Person() { Name = "E" }; - new Person() { Name = "F" }; - new Person() { Name = null }; - new Person() { Name = null }; + _ = new Person(session) { Name = "A", BudgetType = BudgetType.Default }; + _ = new Person(session) { Name = "B", BudgetType = BudgetType.Default }; + _ = new Person(session) { Name = "C", BudgetType = BudgetType.Regional }; + _ = new Person(session) { Name = "D", BudgetType = BudgetType.State }; + _ = new Person(session) { Name = "E" }; + _ = new Person(session) { Name = "F" }; + _ = new Person(session) { Name = null }; + _ = new Person(session) { Name = null }; var selectedMethod = session.Query.All() .Select(p => new PersonDto() {Id = p.Id, Name = p.Name, Description = GetDescription(p)}) .OrderBy(x => x.Name) .ToList(); + Assert.That(selectedMethod.Count, Is.EqualTo(8)); + Assert.That(selectedMethod[0].Name, Is.Null); + Assert.That(selectedMethod[1].Name, Is.Null); + Assert.That(selectedMethod.Skip(2).Select(dto => dto.Name).SequenceEqual(new[] { "A", "B", "C", "D", "E", "F" }), Is.True); + var selectedMethod2 = session.Query.All() .Select(p => new PersonDto() { Id = p.Id, Name = p.Name, Description = p.Name ?? GetDescription(p) }) .Where(x => x.Name == null) .ToList(); + + Assert.That(selectedMethod2.Count, Is.EqualTo(2)); + Assert.That(selectedMethod2[0].Name, Is.Null); + Assert.That(selectedMethod2[1].Name, Is.Null); } } @@ -230,18 +281,18 @@ public void FirstOrDefaultTest() Require.AllFeaturesSupported(ProviderFeatures.ScalarSubqueries); using (var session = Domain.OpenSession()) using (var t = session.OpenTransaction()) { - var manager = new Manager() { + var manager = new Manager(session) { Name = "Manager" }; - var looser = new Manager() { + var looser = new Manager(session) { Name = "Looser" }; - new Person() { Name = "A", BudgetType = BudgetType.Default, Manager = manager}; - new Person() { Name = "B", BudgetType = BudgetType.Default, Manager = manager}; - new Person() { Name = "C", BudgetType = BudgetType.Regional, Manager = manager }; - new Person() { Name = "D", BudgetType = BudgetType.State, Manager = manager }; - new Person() { Name = "E" }; - new Person() { Name = "F" }; + _ = new Person(session) { Name = "A", BudgetType = BudgetType.Default, Manager = manager}; + _ = new Person(session) { Name = "B", BudgetType = BudgetType.Default, Manager = manager}; + _ = new Person(session) { Name = "C", BudgetType = BudgetType.Regional, Manager = manager }; + _ = new Person(session) { Name = "D", BudgetType = BudgetType.State, Manager = manager }; + _ = new Person(session) { Name = "E" }; + _ = new Person(session) { Name = "F" }; var list = session.Query.All() .Select(m => new { Entity = m, FirstPerson = m.Persons.FirstOrDefault() }) @@ -250,6 +301,14 @@ public void FirstOrDefaultTest() FirstPersonId = g.FirstPerson != null ? (int?)g.FirstPerson.Id : null, }) .ToList(); + + Assert.That(list.Count, Is.EqualTo(2)); + foreach(var dto in list) { + var constraint = (dto.Id == looser.Id) + ? Is.Null + : Is.Not.Null; + Assert.That(dto.FirstPersonId, constraint); + } } } diff --git a/Orm/Xtensive.Orm/Orm/Linq/Expressions/Visitors/EnumRewriter.cs b/Orm/Xtensive.Orm/Orm/Linq/Expressions/Visitors/EnumRewriter.cs index 84d5700c61..980e8fa0ed 100644 --- a/Orm/Xtensive.Orm/Orm/Linq/Expressions/Visitors/EnumRewriter.cs +++ b/Orm/Xtensive.Orm/Orm/Linq/Expressions/Visitors/EnumRewriter.cs @@ -11,6 +11,8 @@ namespace Xtensive.Orm.Linq.Expressions.Visitors { internal sealed class EnumRewriter : ExpressionVisitor { + private readonly static System.Collections.Concurrent.ConcurrentDictionary NullableValueCreators = new(); + public static Expression Rewrite(Expression target) { return new EnumRewriter().Visit(target); @@ -41,15 +43,36 @@ private Expression ConvertEnumConstant(ConstantExpression c) { if (c.Type.StripNullable().IsEnum) { var underlyingType = Enum.GetUnderlyingType(c.Type.StripNullable()); - if (c.Type.IsNullable()) - underlyingType = WellKnownTypes.NullableOfT.CachedMakeGenericType(underlyingType); - var underlyingTypeValue = Convert.ChangeType(c.Value, underlyingType); + + object underlyingTypeValue; + if (c.Type.IsNullable()) { + underlyingTypeValue = (c.Value is null) + ? null + : CreateNullableUnderlyingValue(c.Value, underlyingType); + } + else { + underlyingTypeValue = Convert.ChangeType(c.Value, underlyingType); + } var constantExpression = Expression.Constant(underlyingTypeValue); return Expression.Convert(constantExpression, c.Type); } return c; } + private static object CreateNullableUnderlyingValue(object value, Type underlyingType) + { + var instanceCreator = NullableValueCreators.GetOrAdd(underlyingType, (t) => { + var parameter = Expression.Parameter(underlyingType); + var type = WellKnownTypes.NullableOfT.MakeGenericType(underlyingType); + var ctor = type.GetConstructors()[0]; + var body = Expression.New(ctor, parameter); + var instanceCreatorLambda = Expression.Lambda(body, parameter); + return instanceCreatorLambda.Compile(); + }); + + return instanceCreator.DynamicInvoke(value); + } + private EnumRewriter() { } diff --git a/Orm/Xtensive.Orm/Tuples/Packed/TupleLayout.cs b/Orm/Xtensive.Orm/Tuples/Packed/TupleLayout.cs index 2796d85339..ae0e308871 100644 --- a/Orm/Xtensive.Orm/Tuples/Packed/TupleLayout.cs +++ b/Orm/Xtensive.Orm/Tuples/Packed/TupleLayout.cs @@ -60,56 +60,75 @@ private static class ValueFieldAccessorResolver public static ValueFieldAccessor GetValue(Type probeType) { return (probeType.MetadataToken ^ NullableTypeMetadataToken) == 0 - ? ResolveByNullableType(probeType) - : ResolveByType(probeType); - - ValueFieldAccessor ResolveByType(Type type) - { - return ReferenceEquals(type, WellKnownTypes.Int64) ? Int64Accessor : - ReferenceEquals(type, WellKnownTypes.Guid) ? GuidAccessor : - ReferenceEquals(type, WellKnownTypes.Int32) ? Int32Accessor : - ReferenceEquals(type, WellKnownTypes.Bool) ? BoolAccessor : - ReferenceEquals(type, WellKnownTypes.DateTime) ? DateTimeAccessor : - ReferenceEquals(type, WellKnownTypes.TimeSpan) ? TimeSpanAccessor : - ReferenceEquals(type, WellKnownTypes.Double) ? DoubleAccessor : - ReferenceEquals(type, WellKnownTypes.Decimal) ? DecimalAccessor : - ReferenceEquals(type, WellKnownTypes.Single) ? SingleAccessor : - ReferenceEquals(type, WellKnownTypes.DateTimeOffset) ? DateTimeOffsetAccessor : + ? (ResolveByNullableType(probeType) ?? TryResolveNullableEnum(probeType)) + : (ResolveByType(probeType) ?? TryResolveEnum(probeType)); + } + + private static ValueFieldAccessor ResolveByType(Type type) + { + return ReferenceEquals(type, WellKnownTypes.Int64) ? Int64Accessor : + ReferenceEquals(type, WellKnownTypes.Guid) ? GuidAccessor : + ReferenceEquals(type, WellKnownTypes.Int32) ? Int32Accessor : + ReferenceEquals(type, WellKnownTypes.Bool) ? BoolAccessor : + ReferenceEquals(type, WellKnownTypes.DateTime) ? DateTimeAccessor : + ReferenceEquals(type, WellKnownTypes.TimeSpan) ? TimeSpanAccessor : + ReferenceEquals(type, WellKnownTypes.Double) ? DoubleAccessor : + ReferenceEquals(type, WellKnownTypes.Decimal) ? DecimalAccessor : + ReferenceEquals(type, WellKnownTypes.Single) ? SingleAccessor : + ReferenceEquals(type, WellKnownTypes.DateTimeOffset) ? DateTimeOffsetAccessor : #if NET6_0_OR_GREATER - ReferenceEquals(type, WellKnownTypes.DateOnly) ? DateOnlyAccessor : - ReferenceEquals(type, WellKnownTypes.TimeOnly) ? TimeOnlyAccessor : + ReferenceEquals(type, WellKnownTypes.DateOnly) ? DateOnlyAccessor : + ReferenceEquals(type, WellKnownTypes.TimeOnly) ? TimeOnlyAccessor : #endif - ReferenceEquals(type, WellKnownTypes.Int16) ? Int16Accessor : - ReferenceEquals(type, WellKnownTypes.Byte) ? ByteAccessor : - ReferenceEquals(type, WellKnownTypes.SByte) ? SByteAccessor : - ReferenceEquals(type, WellKnownTypes.UInt16) ? UInt16Accessor : - ReferenceEquals(type, WellKnownTypes.UInt32) ? UInt32Accessor : - ReferenceEquals(type, WellKnownTypes.UInt64) ? UInt64Accessor : null; - } + ReferenceEquals(type, WellKnownTypes.Int16) ? Int16Accessor : + ReferenceEquals(type, WellKnownTypes.Byte) ? ByteAccessor : + ReferenceEquals(type, WellKnownTypes.SByte) ? SByteAccessor : + ReferenceEquals(type, WellKnownTypes.UInt16) ? UInt16Accessor : + ReferenceEquals(type, WellKnownTypes.UInt32) ? UInt32Accessor : + ReferenceEquals(type, WellKnownTypes.UInt64) ? UInt64Accessor : null; + } - ValueFieldAccessor ResolveByNullableType(Type type) - { - return ReferenceEquals(type, WellKnownTypes.NullableBool) ? BoolAccessor : - ReferenceEquals(type, WellKnownTypes.NullableInt32) ? Int32Accessor : - ReferenceEquals(type, WellKnownTypes.NullableDouble) ? DoubleAccessor : - ReferenceEquals(type, WellKnownTypes.NullableDecimal) ? DecimalAccessor : - ReferenceEquals(type, WellKnownTypes.NullableInt64) ? Int64Accessor : - ReferenceEquals(type, WellKnownTypes.NullableDateTime) ? DateTimeAccessor : - ReferenceEquals(type, WellKnownTypes.NullableTimeSpan) ? TimeSpanAccessor : - ReferenceEquals(type, WellKnownTypes.NullableDateTimeOffset) ? DateTimeOffsetAccessor : - ReferenceEquals(type, WellKnownTypes.NullableSingle) ? SingleAccessor : + private static ValueFieldAccessor ResolveByNullableType(Type type) + { + return ReferenceEquals(type, WellKnownTypes.NullableBool) ? BoolAccessor : + ReferenceEquals(type, WellKnownTypes.NullableInt32) ? Int32Accessor : + ReferenceEquals(type, WellKnownTypes.NullableDouble) ? DoubleAccessor : + ReferenceEquals(type, WellKnownTypes.NullableDecimal) ? DecimalAccessor : + ReferenceEquals(type, WellKnownTypes.NullableInt64) ? Int64Accessor : + ReferenceEquals(type, WellKnownTypes.NullableDateTime) ? DateTimeAccessor : + ReferenceEquals(type, WellKnownTypes.NullableTimeSpan) ? TimeSpanAccessor : + ReferenceEquals(type, WellKnownTypes.NullableDateTimeOffset) ? DateTimeOffsetAccessor : + ReferenceEquals(type, WellKnownTypes.NullableSingle) ? SingleAccessor : #if NET6_0_OR_GREATER - ReferenceEquals(type, WellKnownTypes.NullableDateOnly) ? DateOnlyAccessor : - ReferenceEquals(type, WellKnownTypes.NullableTimeOnly) ? TimeOnlyAccessor : + ReferenceEquals(type, WellKnownTypes.NullableDateOnly) ? DateOnlyAccessor : + ReferenceEquals(type, WellKnownTypes.NullableTimeOnly) ? TimeOnlyAccessor : #endif - ReferenceEquals(type, WellKnownTypes.NullableGuid) ? GuidAccessor : - ReferenceEquals(type, WellKnownTypes.NullableInt16) ? Int16Accessor : - ReferenceEquals(type, WellKnownTypes.NullableByte) ? ByteAccessor : - ReferenceEquals(type, WellKnownTypes.NullableSByte) ? SByteAccessor : - ReferenceEquals(type, WellKnownTypes.NullableUInt16) ? UInt16Accessor : - ReferenceEquals(type, WellKnownTypes.NullableUInt32) ? UInt32Accessor : - ReferenceEquals(type, WellKnownTypes.NullableUInt64) ? UInt64Accessor : null; + ReferenceEquals(type, WellKnownTypes.NullableGuid) ? GuidAccessor : + ReferenceEquals(type, WellKnownTypes.NullableInt16) ? Int16Accessor : + ReferenceEquals(type, WellKnownTypes.NullableByte) ? ByteAccessor : + ReferenceEquals(type, WellKnownTypes.NullableSByte) ? SByteAccessor : + ReferenceEquals(type, WellKnownTypes.NullableUInt16) ? UInt16Accessor : + ReferenceEquals(type, WellKnownTypes.NullableUInt32) ? UInt32Accessor : + ReferenceEquals(type, WellKnownTypes.NullableUInt64) ? UInt64Accessor : null; + } + + private static ValueFieldAccessor TryResolveEnum(Type type) + { + if (!type.IsEnum) { + return null; + } + type = Enum.GetUnderlyingType(type); + return ResolveByType(type); + } + + private static ValueFieldAccessor TryResolveNullableEnum(Type type) + { + var generic = type.GetGenericArguments()[0]; + if (!generic.IsEnum) { + return null; } + type = Enum.GetUnderlyingType(generic); + return ResolveByType(type); } }