Skip to content

Commit

Permalink
fix: anonymous object support in CustomOperators #76
Browse files Browse the repository at this point in the history
  • Loading branch information
alirezanet committed Apr 12, 2022
1 parent 7375b53 commit 039840e
Show file tree
Hide file tree
Showing 6 changed files with 297 additions and 2 deletions.
15 changes: 15 additions & 0 deletions gridify.sln
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityFrameworkIntegrationT
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityFrameworkSqlProviderIntegrationTests", "test\EntityFrameworkSqlProviderIntegrationTests\EntityFrameworkSqlProviderIntegrationTests.csproj", "{3B9A8E46-1D4D-40EE-89C0-C3C376D9320A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityFrameworkPostgreSqlIntegrationTests", "test\EntityFrameworkPostgreSqlIntegrationTests\EntityFrameworkPostgreSqlIntegrationTests.csproj", "{7C6699E7-7B6E-48D4-920F-6DD3568FBFD9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -115,6 +117,18 @@ Global
{3B9A8E46-1D4D-40EE-89C0-C3C376D9320A}.Release|x64.Build.0 = Release|Any CPU
{3B9A8E46-1D4D-40EE-89C0-C3C376D9320A}.Release|x86.ActiveCfg = Release|Any CPU
{3B9A8E46-1D4D-40EE-89C0-C3C376D9320A}.Release|x86.Build.0 = Release|Any CPU
{7C6699E7-7B6E-48D4-920F-6DD3568FBFD9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7C6699E7-7B6E-48D4-920F-6DD3568FBFD9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7C6699E7-7B6E-48D4-920F-6DD3568FBFD9}.Debug|x64.ActiveCfg = Debug|Any CPU
{7C6699E7-7B6E-48D4-920F-6DD3568FBFD9}.Debug|x64.Build.0 = Debug|Any CPU
{7C6699E7-7B6E-48D4-920F-6DD3568FBFD9}.Debug|x86.ActiveCfg = Debug|Any CPU
{7C6699E7-7B6E-48D4-920F-6DD3568FBFD9}.Debug|x86.Build.0 = Debug|Any CPU
{7C6699E7-7B6E-48D4-920F-6DD3568FBFD9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7C6699E7-7B6E-48D4-920F-6DD3568FBFD9}.Release|Any CPU.Build.0 = Release|Any CPU
{7C6699E7-7B6E-48D4-920F-6DD3568FBFD9}.Release|x64.ActiveCfg = Release|Any CPU
{7C6699E7-7B6E-48D4-920F-6DD3568FBFD9}.Release|x64.Build.0 = Release|Any CPU
{7C6699E7-7B6E-48D4-920F-6DD3568FBFD9}.Release|x86.ActiveCfg = Release|Any CPU
{7C6699E7-7B6E-48D4-920F-6DD3568FBFD9}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{CDFDBB16-1D9F-40FD-B693-96D1D4FB79EE} = {1BBCBA37-25E5-4BFF-A8E8-7EE582E0317F}
Expand All @@ -124,5 +138,6 @@ Global
{02F96021-6989-4F09-919D-999F0BE3DEFB} = {41676937-4F05-4794-A2E5-442127927776}
{9A54A635-2B5B-4EDC-98FE-9BF963903BD4} = {1BBCBA37-25E5-4BFF-A8E8-7EE582E0317F}
{3B9A8E46-1D4D-40EE-89C0-C3C376D9320A} = {1BBCBA37-25E5-4BFF-A8E8-7EE582E0317F}
{7C6699E7-7B6E-48D4-920F-6DD3568FBFD9} = {1BBCBA37-25E5-4BFF-A8E8-7EE582E0317F}
EndGlobalSection
EndGlobal
13 changes: 11 additions & 2 deletions src/Gridify/Syntax/SyntaxTreeToQueryConvertor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ private static LambdaExpression GetExpressionWithNullCheck(MemberExpression prop
if (allowNullSearch && op.Kind is SyntaxKind.Equal or SyntaxKind.NotEqual && value.ToString() == "null")
value = null;

var isConvertable = true;

// type fixer
if (value is not null && body.Type != value.GetType())
{
Expand All @@ -173,10 +175,13 @@ private static LambdaExpression GetExpressionWithNullCheck(MemberExpression prop
if (body.Type == typeof(Guid) && !Guid.TryParse(value.ToString(), out _)) value = Guid.NewGuid().ToString();

var converter = TypeDescriptor.GetConverter(body.Type);
value = converter.ConvertFromString(value.ToString()!);
isConvertable = converter.CanConvertFrom(typeof(string));
if (isConvertable)
value = converter.ConvertFromString(value.ToString()!);
}
catch (FormatException)
{
// this code should never run
// return no records in case of any exception in formatting
return Expression.Lambda(Expression.Constant(false), parameter); // q => false
}
Expand Down Expand Up @@ -306,7 +311,11 @@ private static LambdaExpression GetExpressionWithNullCheck(MemberExpression prop
var customOperator = GridifyGlobalConfiguration.CustomOperators.Operators.First(q => q.GetOperator() == token!.Text);
var customExp = customOperator.OperatorHandler();
be = new ReplaceExpressionVisitor(customExp.Parameters[0], body).Visit(customExp.Body);
be = new ReplaceExpressionVisitor(customExp.Parameters[1], Expression.Constant(value, body.Type)).Visit(be);
if (isConvertable)
be = new ReplaceExpressionVisitor(customExp.Parameters[1], Expression.Constant(value, body.Type)).Visit(be);

be = new ReplaceExpressionVisitor(customExp.Parameters[1], Expression.Constant(value, typeof(string))).Visit(be);

break;
default:
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.3" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.2" />
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Gridify.EntityFramework\Gridify.EntityFramework.csproj" />
<ProjectReference Include="..\..\src\Gridify\Gridify.csproj" />
</ItemGroup>
</Project>
153 changes: 153 additions & 0 deletions test/EntityFrameworkPostgreSqlIntegrationTests/Interceptors.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Diagnostics;

namespace EntityFrameworkIntegrationTests.cs;

public class SuppressConnectionInterceptor : DbConnectionInterceptor
{
public override ValueTask<InterceptionResult> ConnectionOpeningAsync(DbConnection connection, ConnectionEventData eventData,
InterceptionResult result,
CancellationToken cancellationToken = new())
{
result = InterceptionResult.Suppress();
return base.ConnectionOpeningAsync(connection, eventData, result, cancellationToken);
}

public override InterceptionResult ConnectionOpening(DbConnection connection, ConnectionEventData eventData, InterceptionResult result)
{
result = InterceptionResult.Suppress();
return base.ConnectionOpening(connection, eventData, result);
}
}

public class EmptyMessageDataReader : DbDataReader
{

private readonly List<User> _users = new List<User>();

public EmptyMessageDataReader()
{
}

public override int FieldCount
=> 0;

public override int RecordsAffected
=> 0;

public override bool HasRows
=> false;

public override bool IsClosed
=> true;

public override int Depth
=> 0;

public override bool Read()
=> false;

public override int GetInt32(int ordinal)
=> 0;

public override bool IsDBNull(int ordinal)
=> false;

public override string GetString(int ordinal)
=> "suppressed message";

public override bool GetBoolean(int ordinal)
=> true;

public override byte GetByte(int ordinal)
=> 0;

public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length)
=> 0;

public override char GetChar(int ordinal)
=> '\0';

public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length)
=> 0;

public override string GetDataTypeName(int ordinal)
=> string.Empty;

public override DateTime GetDateTime(int ordinal)
=> DateTime.Now;

public override decimal GetDecimal(int ordinal)
=> 0;

public override double GetDouble(int ordinal)
=> 0;

public override Type GetFieldType(int ordinal)
=> typeof(User);

public override float GetFloat(int ordinal)
=> 0;

public override Guid GetGuid(int ordinal)
=> Guid.Empty;

public override short GetInt16(int ordinal)
=> 0;

public override long GetInt64(int ordinal)
=> 0;

public override string GetName(int ordinal)
=> "";

public override int GetOrdinal(string name)
=> 0;

public override object GetValue(int ordinal)
=> new object();

public override int GetValues(object[] values)
=> 0;

public override object this[int ordinal]
=> new object();

public override object this[string name]
=> new object();

public override bool NextResult()
=> false;

public override IEnumerator GetEnumerator()
=> _users.GetEnumerator();
}

public class SuppressCommandResultInterceptor : DbCommandInterceptor
{
public override InterceptionResult<DbDataReader> ReaderExecuting(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result)
{
result = InterceptionResult<DbDataReader>.SuppressWithResult(new EmptyMessageDataReader());

return result;
}

public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result,
CancellationToken cancellationToken = default)
{
result = InterceptionResult<DbDataReader>.SuppressWithResult(new EmptyMessageDataReader());

return new ValueTask<InterceptionResult<DbDataReader>>(result);
}
}
46 changes: 46 additions & 0 deletions test/EntityFrameworkPostgreSqlIntegrationTests/Issue76Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System.Linq.Expressions;
using System.Reflection.Metadata;
using EntityFrameworkIntegrationTests.cs;
using Gridify.Syntax;
using Microsoft.EntityFrameworkCore;
using Xunit;

namespace Gridify.Tests;

public class Issue76Tests
{

private readonly MyDbContext _dbContext;

public Issue76Tests()
{
_dbContext = new MyDbContext();
}

[Fact]
public void CustomOperator_EFJsonContains_ShouldGenerateCorrectExpression()
{
// Arrange
GridifyGlobalConfiguration.CustomOperators.Register(new JsonContainsOperator());

var gm = new GridifyMapper<Products>()
.AddMap("u", q => q.Users);
var expected = _dbContext.ProductsViews.Where(q => EF.Functions.JsonContains(q.Users, new[] { new { Id = 1 } })).ToQueryString();

// Act
var actual = _dbContext.ProductsViews.ApplyFiltering("u #= 1", gm).ToQueryString();

// Assert
Assert.Equal(expected, actual);
}

}

public class JsonContainsOperator : IGridifyOperator
{
public string GetOperator() => "#=";
public Expression<OperatorParameter> OperatorHandler()
{
return (prop, value) => EF.Functions.JsonContains(prop, new[] { new { Id = int.Parse(value.ToString()!) } });
}
}
50 changes: 50 additions & 0 deletions test/EntityFrameworkPostgreSqlIntegrationTests/MyDbContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;

namespace EntityFrameworkIntegrationTests.cs;

public class MyDbContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Products> ProductsViews { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().Property<string>("shadow1");
base.OnModelCreating(modelBuilder);
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql("Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=myPassword;");
optionsBuilder.AddInterceptors(new SuppressCommandResultInterceptor());
optionsBuilder.AddInterceptors(new SuppressConnectionInterceptor());
optionsBuilder.EnableServiceProviderCaching();

base.OnConfiguring(optionsBuilder);
}
}

public class User
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime? CreateDate { get; set; }
public Guid FkGuid { get; set; }
}
public class Products
{
public int Id { get; set; }
public string Name { get; set; }

[Column(TypeName = "jsonb")]
public IEnumerable<ProductUser> Users { get; set; }
}

public class ProductUser
{
public int Id { get; set; }
public string Name { get; set; }
}

0 comments on commit 039840e

Please sign in to comment.