From e07a827df242f9dbcdf49f0f1e9ca2e59c97ddf4 Mon Sep 17 00:00:00 2001 From: Alexander Marek Date: Tue, 30 Jan 2018 17:27:55 +0100 Subject: [PATCH 1/3] Fixed Effort- and ODataQueryTests and a bug in ExpressionVisitor #2520 --- src/AutoMapper/Mappers/ExpressionMapper.cs | 8 +++- src/AutoMapperSamples.EF/EffortQueryTests.cs | 48 ++++++++++++++----- .../ODataQueryTests.cs | 4 +- .../OrdersController.cs | 15 +++--- 4 files changed, 52 insertions(+), 23 deletions(-) diff --git a/src/AutoMapper/Mappers/ExpressionMapper.cs b/src/AutoMapper/Mappers/ExpressionMapper.cs index ac4ee454a0..efa303a80e 100644 --- a/src/AutoMapper/Mappers/ExpressionMapper.cs +++ b/src/AutoMapper/Mappers/ExpressionMapper.cs @@ -90,10 +90,14 @@ protected override Expression VisitBinary(BinaryExpression node) { var newLeft = Visit(node.Left); var newRight = Visit(node.Right); + + // check if the non-string expression is a null constent + // as this would lead to a "null.ToString()" and thus an error when executing the expression + var isNullConstant = new Func(xpr => xpr is ConstantExpression c && c.Value == null); - if (newLeft.Type != newRight.Type && newRight.Type == typeof(string)) + if (newLeft.Type != newRight.Type && newRight.Type == typeof(string) && !isNullConstant(newLeft)) newLeft = Call(newLeft, typeof(object).GetDeclaredMethod("ToString")); - if (newRight.Type != newLeft.Type && newLeft.Type == typeof(string)) + if (newRight.Type != newLeft.Type && newLeft.Type == typeof(string) && !isNullConstant(newRight)) newRight = Call(newRight, typeof(object).GetDeclaredMethod("ToString")); CheckNullableToNonNullableChanges(node.Left, node.Right, ref newLeft, ref newRight); CheckNullableToNonNullableChanges(node.Right, node.Left, ref newRight, ref newLeft); diff --git a/src/AutoMapperSamples.EF/EffortQueryTests.cs b/src/AutoMapperSamples.EF/EffortQueryTests.cs index 0220564671..c2acb346b2 100644 --- a/src/AutoMapperSamples.EF/EffortQueryTests.cs +++ b/src/AutoMapperSamples.EF/EffortQueryTests.cs @@ -15,6 +15,9 @@ namespace AutoMapperSamples.EF [TestFixture] public class EffortQueryTests { + private Mapper _mapper; + private MapperConfiguration _config; + static EffortQueryTests() { Effort.Provider.EffortProviderConfiguration.RegisterProvider(); @@ -23,7 +26,10 @@ static EffortQueryTests() [SetUp] public void SetUp() { - Mapper.Initialize(MappingConfiguration.Configure); + //Mapper.Initialize(MappingConfiguration.Configure); + + _config = new MapperConfiguration(MappingConfiguration.Configure); + _mapper = new Mapper(_config); } [Test] @@ -34,8 +40,8 @@ public void Effort_FilterByDto() IQueryable sourceResult = new OrderDto[0] .AsQueryable() .Where(s => s.FullName.EndsWith("Bestellung")) - .Map(context.OrderSet) - .ProjectTo(); // projection added + .Map(context.OrderSet, _config) + .ProjectTo(_config); // projection added var dtos = sourceResult.ToList(); @@ -54,8 +60,8 @@ public void Effort_FilterByMappedQuery() IQueryable sourceResult = new OrderDto[0] .AsQueryable() .Where(s => s.FullName.EndsWith("Bestellung")) - .Map(context.OrderSet) - .ProjectTo(); // projection added + .Map(context.OrderSet, _config) + .ProjectTo(_config); // projection added var dtos = sourceResult.ToList(); Assert.AreEqual(2, dtos.Count); @@ -70,8 +76,8 @@ public void Effort_FilterByMappedQuery() { IQueryable sourceResult2 = new OrderDto[0] .AsQueryable() - .Map(context.OrderSet) - .ProjectTo(); // projection added + .Map(context.OrderSet, _config) + .ProjectTo(_config); // projection added var dtos2 = sourceResult .Where(s => s.FullName.EndsWith("Bestellung")) .ToList(); @@ -81,9 +87,9 @@ public void Effort_FilterByMappedQuery() catch (NotSupportedException) { } - + // Using "AsDataSource" - IQueryable sourceResult4 = context.OrderSet.UseAsDataSource(Mapper.Configuration).For(); + IQueryable sourceResult4 = context.OrderSet.UseAsDataSource(_config).For(); var dtos4 = sourceResult4.Where(d => d.FullName.EndsWith("Bestellung")).ToList(); Assert.AreEqual(2, dtos4.Count); @@ -97,7 +103,7 @@ public void Effort_FilterByMappedQuery_InnerQueryOrderedAndFiltered() { var orders = context.OrderSet.Where(o => o.Price > 85D).OrderBy(o => o.Price); - IQueryable lazilyMappedQuery = orders.UseAsDataSource(Mapper.Configuration).For(); + IQueryable lazilyMappedQuery = orders.UseAsDataSource(_mapper).For(); var dtos3 = lazilyMappedQuery .Where(d => d.FullName.EndsWith("Bestellung")).ToList(); @@ -112,7 +118,7 @@ public void Effort_OrderByDto_FullName() { var orders = context.OrderSet; - IQueryable lazilyMappedQuery = orders.UseAsDataSource(Mapper.Configuration).For(); + IQueryable lazilyMappedQuery = orders.UseAsDataSource(_mapper).For(); var dtos3 = lazilyMappedQuery .OrderBy(dto => dto.FullName).Skip(2).ToList(); @@ -121,6 +127,22 @@ public void Effort_OrderByDto_FullName() } } + [Test] + public void Effort_FilterByDto_FullName_Contains() + { + using (var context = new TestDbContext(Effort.DbConnectionFactory.CreateTransient())) + { + var orders = context.OrderSet; + + IQueryable lazilyMappedQuery = orders.UseAsDataSource(_mapper).For(); + var dtos3 = lazilyMappedQuery + .Where(dto => dto.FullName.Contains("Zalando")).ToList(); + + Assert.AreEqual(1, dtos3.Count); + Assert.AreEqual("Zalando Bestellung", dtos3.Single().FullName); + } + } + [Test] public void Effort_OrderByDto_Price() { @@ -128,7 +150,7 @@ public void Effort_OrderByDto_Price() { var orders = context.OrderSet; - IQueryable lazilyMappedQuery = orders.UseAsDataSource(Mapper.Configuration).For(); + IQueryable lazilyMappedQuery = orders.UseAsDataSource(_mapper).For(); var dtos3 = lazilyMappedQuery .OrderBy(dto => dto.Price).Skip(2).ToList(); @@ -144,7 +166,7 @@ public void Effort_FilterByMappedQuery_SkipAndTake() { var orders = context.OrderSet.OrderBy(o => o.Name); - IQueryable lazilyMappedQuery = orders.UseAsDataSource(Mapper.Configuration).For(); + IQueryable lazilyMappedQuery = orders.UseAsDataSource(_mapper).For(); var dtos3 = lazilyMappedQuery .Skip(1).Take(1).ToList(); diff --git a/src/AutoMapperSamples.OData/ODataQueryTests.cs b/src/AutoMapperSamples.OData/ODataQueryTests.cs index beb4c24290..c2cd3bb005 100644 --- a/src/AutoMapperSamples.OData/ODataQueryTests.cs +++ b/src/AutoMapperSamples.OData/ODataQueryTests.cs @@ -35,7 +35,7 @@ public virtual void SetUp() // Start OWIN host _webApp = WebApp.Start(url: _baseAddress); } - + [TearDown] public virtual void TearDown() { @@ -325,7 +325,7 @@ public void CanFilter_OrderDateGt() // Act var response = client.GetAsync(_baseAddress + "api/Orders?$filter=OrderDate gt DateTime'2015-02-01T00:00:00'").Result; - + // Assert Console.WriteLine(response); Console.WriteLine(response.Content.ReadAsStringAsync().Result); diff --git a/src/AutoMapperSamples.OData/OrdersController.cs b/src/AutoMapperSamples.OData/OrdersController.cs index ef54dd9899..e81b01dae7 100644 --- a/src/AutoMapperSamples.OData/OrdersController.cs +++ b/src/AutoMapperSamples.OData/OrdersController.cs @@ -16,11 +16,14 @@ namespace AutoMapperSamples.OData public class OrdersController : ApiController { private TestDbContext context = null; + private MapperConfiguration _config; + private Mapper _mapper; internal static Action OnException { get; set; } public OrdersController() { - Mapper.Initialize(MappingConfiguration.Configure); + _config = new MapperConfiguration(MappingConfiguration.Configure); + _mapper = new Mapper(_config); context = new TestDbContext(Effort.DbConnectionFactory.CreateTransient()); } @@ -28,7 +31,7 @@ public OrdersController() [EnableQuery] public IQueryable Get() { - return context.OrderSet.Include("Customer").UseAsDataSource(Mapper.Instance) + return context.OrderSet.Include("Customer").UseAsDataSource(_mapper) // add an optional exceptionhandler that will be invoked // in case an exception is raised upon query execution. // otherwise it would get lost on the WebApi side and all we would get would be @@ -53,10 +56,10 @@ public IQueryable Get() var customerIds = enumerator.OfType().Select(o => o.Customer.Id); // add IDs of orders var customersOrders = context.CustomerSet - .Include("Orders") - .Where(c => customerIds.Contains(c.Id)) - .Select(c => new { CustomerId = c.Id, OrderIds = c.Orders.Select(o => o.Id) }) - .ToDictionary(c => c.CustomerId); + .Include("Orders") + .Where(c => customerIds.Contains(c.Id)) + .Select(c => new { CustomerId = c.Id, OrderIds = c.Orders.Select(o => o.Id) }) + .ToDictionary(c => c.CustomerId); // apply the list of IDs to each OrderDto foreach (var order in enumerator.OfType()) { From cf9af9c64edef5b76d15976bf9c6cae1ecdc91a7 Mon Sep 17 00:00:00 2001 From: Alexander Marek Date: Tue, 30 Jan 2018 17:40:55 +0100 Subject: [PATCH 2/3] Changed isNullConstant func to local method --- src/AutoMapper/Mappers/ExpressionMapper.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/AutoMapper/Mappers/ExpressionMapper.cs b/src/AutoMapper/Mappers/ExpressionMapper.cs index efa303a80e..1f23d2ed1e 100644 --- a/src/AutoMapper/Mappers/ExpressionMapper.cs +++ b/src/AutoMapper/Mappers/ExpressionMapper.cs @@ -93,11 +93,14 @@ protected override Expression VisitBinary(BinaryExpression node) // check if the non-string expression is a null constent // as this would lead to a "null.ToString()" and thus an error when executing the expression - var isNullConstant = new Func(xpr => xpr is ConstantExpression c && c.Value == null); + bool IsNullConstant(Expression xpr) + { + return xpr is ConstantExpression c && c.Value == null; + } - if (newLeft.Type != newRight.Type && newRight.Type == typeof(string) && !isNullConstant(newLeft)) + if (newLeft.Type != newRight.Type && newRight.Type == typeof(string) && !IsNullConstant(newLeft)) newLeft = Call(newLeft, typeof(object).GetDeclaredMethod("ToString")); - if (newRight.Type != newLeft.Type && newLeft.Type == typeof(string) && !isNullConstant(newRight)) + if (newRight.Type != newLeft.Type && newLeft.Type == typeof(string) && !IsNullConstant(newRight)) newRight = Call(newRight, typeof(object).GetDeclaredMethod("ToString")); CheckNullableToNonNullableChanges(node.Left, node.Right, ref newLeft, ref newRight); CheckNullableToNonNullableChanges(node.Right, node.Left, ref newRight, ref newLeft); From fa451c8dc3bf65d4ae4eba97384b0e82286df53b Mon Sep 17 00:00:00 2001 From: Lucian Bargaoanu Date: Tue, 30 Jan 2018 20:29:28 +0200 Subject: [PATCH 3/3] cosmetic --- src/AutoMapper/Mappers/ExpressionMapper.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/AutoMapper/Mappers/ExpressionMapper.cs b/src/AutoMapper/Mappers/ExpressionMapper.cs index 1f23d2ed1e..6c67aa8a8b 100644 --- a/src/AutoMapper/Mappers/ExpressionMapper.cs +++ b/src/AutoMapper/Mappers/ExpressionMapper.cs @@ -90,14 +90,9 @@ protected override Expression VisitBinary(BinaryExpression node) { var newLeft = Visit(node.Left); var newRight = Visit(node.Right); - - // check if the non-string expression is a null constent - // as this would lead to a "null.ToString()" and thus an error when executing the expression - bool IsNullConstant(Expression xpr) - { - return xpr is ConstantExpression c && c.Value == null; - } + // check if the non-string expression is a null constent + // as this would lead to a "null.ToString()" and thus an error when executing the expression if (newLeft.Type != newRight.Type && newRight.Type == typeof(string) && !IsNullConstant(newLeft)) newLeft = Call(newLeft, typeof(object).GetDeclaredMethod("ToString")); if (newRight.Type != newLeft.Type && newLeft.Type == typeof(string) && !IsNullConstant(newRight)) @@ -105,6 +100,7 @@ bool IsNullConstant(Expression xpr) CheckNullableToNonNullableChanges(node.Left, node.Right, ref newLeft, ref newRight); CheckNullableToNonNullableChanges(node.Right, node.Left, ref newRight, ref newLeft); return MakeBinary(node.NodeType, newLeft, newRight); + bool IsNullConstant(Expression expression) => expression is ConstantExpression constant && constant.Value == null; } private static void CheckNullableToNonNullableChanges(Expression left, Expression right, ref Expression newLeft, ref Expression newRight)