Skip to content

Commit 905eecb

Browse files
committed
fixed property mapping
1 parent e7a3230 commit 905eecb

File tree

10 files changed

+104
-75
lines changed

10 files changed

+104
-75
lines changed

example/Controllers/UserController.cs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using AutoMapper;
2-
using AutoMapper.QueryableExtensions;
31
using Microsoft.AspNetCore.Mvc;
42
using Microsoft.EntityFrameworkCore;
53

@@ -8,25 +6,22 @@
86
public class UsersController : ControllerBase
97
{
108
private readonly ApplicationDbContext _db;
11-
private readonly IMapper _mapper;
12-
13-
public UsersController(ApplicationDbContext db, IMapper mapper)
9+
public UsersController(ApplicationDbContext db)
1410
{
1511
_db = db;
16-
_mapper = mapper;
1712
}
1813

1914
// GET: /controller/users
2015
[HttpGet]
21-
[ProducesResponseType(typeof(IEnumerable<UserDto>), StatusCodes.Status200OK)]
22-
[EnableQuery<UserDto>(maxTop: 10)]
23-
public ActionResult<IEnumerable<UserDto>> Get()
16+
[ProducesResponseType(typeof(IEnumerable<User>), StatusCodes.Status200OK)]
17+
[EnableQuery<User>(maxTop: 10)]
18+
public ActionResult<IEnumerable<User>> Get()
2419
{
2520
var users = _db.Users
21+
.Include(x => x.Company)
2622
.Include(x => x.Manager)
2723
.ThenInclude(x => x.Manager)
28-
.Where(x => !x.IsDeleted)
29-
.ProjectTo<UserDto>(_mapper.ConfigurationProvider);
24+
.Where(x => !x.IsDeleted);
3025

3126
return Ok(users);
3227
}

example/Dto/UserDto.cs

Lines changed: 0 additions & 31 deletions
This file was deleted.

example/Entities/User.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public record User
1919
public User? Manager { get; set; }
2020
public IEnumerable<Address> Addresses { get; set; } = Array.Empty<Address>();
2121
public IEnumerable<string> Tags { get; set; } = Array.Empty<string>();
22+
public Company? Company { get; set; }
2223
}
2324

2425
public record Address
@@ -33,4 +34,11 @@ public record City
3334
public Guid Id { get; set; }
3435
public string Name { get; set; } = string.Empty;
3536
public string Country { get; set; } = string.Empty;
37+
}
38+
39+
public record Company
40+
{
41+
public Guid Id { get; set; }
42+
public string Name { get; set; } = string.Empty;
43+
public string Department { get; set; } = string.Empty;
3644
}

example/Profiles/AutoMapperProfile.cs

Lines changed: 0 additions & 11 deletions
This file was deleted.

example/Program.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
using System.Reflection;
2-
using AutoMapper;
3-
using AutoMapper.QueryableExtensions;
42
using Bogus;
53
using Microsoft.AspNetCore.Mvc;
64
using Microsoft.EntityFrameworkCore;
@@ -23,8 +21,6 @@
2321
options.UseNpgsql(postgreSqlContainer.GetConnectionString());
2422
});
2523

26-
builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());
27-
2824
var app = builder.Build();
2925

3026
using (var scope = app.Services.CreateScope())
@@ -35,6 +31,10 @@
3531
// Seed data
3632
if (!context.Users.Any())
3733
{
34+
var companies = new Faker<Company>()
35+
.RuleFor(x => x.Name, f => f.Company.CompanyName())
36+
.RuleFor(x => x.Department, f => f.Commerce.Department());
37+
3838
var cities = new Faker<City>()
3939
.RuleFor(x => x.Name, f => f.Address.City())
4040
.RuleFor(x => x.Country, f => f.Address.Country());
@@ -61,7 +61,8 @@
6161
})
6262
.RuleFor(x => x.Manager, (f, u) => f.CreateManager(3))
6363
.RuleFor(x => x.Addresses, f => f.PickRandom(addresses.Generate(5), f.Random.Int(1, 3)).ToList())
64-
.RuleFor(x => x.Tags, f => f.Lorem.Words(f.Random.Int(0, 5)).ToList());
64+
.RuleFor(x => x.Tags, f => f.Lorem.Words(f.Random.Int(0, 5)).ToList())
65+
.RuleFor(x => x.Company, f => f.PickRandom(companies.Generate(20)));
6566

6667
context.Users.AddRange(users.Generate(1_000));
6768
context.SaveChanges();
@@ -72,21 +73,20 @@
7273

7374
Console.WriteLine($"Postgres connection string: {postgreSqlContainer.GetConnectionString()}");
7475

75-
app.MapGet("/minimal/users", (ApplicationDbContext db, [FromServices] IMapper mapper, [AsParameters] Query query) =>
76+
app.MapGet("/minimal/users", (ApplicationDbContext db, [AsParameters] Query query) =>
7677
{
7778
var result = db.Users
79+
.Include(x => x.Company)
7880
.Include(x => x.Manager)
7981
.ThenInclude(x => x.Manager)
80-
.Where(x => !x.IsDeleted)
81-
.ProjectTo<UserDto>(mapper.ConfigurationProvider)
8282
.Apply(query);
8383

8484
if (result.IsFailed)
8585
{
8686
return Results.BadRequest(new { message = result.Errors });
8787
}
8888

89-
var response = new PagedResponse<UserDto>(result.Value.Query.ToList(), result.Value.Count);
89+
var response = new PagedResponse<User>(result.Value.Query.ToList(), result.Value.Count);
9090

9191
return Results.Ok(response);
9292
});

example/example.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10-
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
1110
<PackageReference Include="Bogus" Version="35.6.3" />
1211
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
1312
<PackageReference Include="Testcontainers" Version="4.5.0" />

src/GoatQuery/src/Evaluator/FilterEvaluator.cs

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,24 +76,69 @@ private static Result<Expression> EvaluatePropertyPathExpression(
7676
{
7777
var current = startExpression;
7878
var nullChecks = new List<Expression>();
79+
var currentPropertyMapping = propertyMapping;
7980

8081
foreach (var (segment, isLast) in propertyPath.Segments.Select((s, i) => (s, i == propertyPath.Segments.Count - 1)))
8182
{
82-
if (!propertyMapping.TryGetValue(segment, out var propertyName))
83-
return Result.Fail($"Invalid property '{segment}' in path");
83+
var propertyResult = ResolvePropertySegment(segment, current, currentPropertyMapping);
84+
if (propertyResult.IsFailed) return Result.Fail(propertyResult.Errors);
8485

85-
current = Expression.Property(current, propertyName);
86+
current = propertyResult.Value;
8687

8788
// Add null check for intermediate reference types only (not the final property)
8889
if (!isLast && IsNullableReferenceType(current.Type))
8990
{
9091
nullChecks.Add(Expression.NotEqual(current, Expression.Constant(null, current.Type)));
9192
}
93+
94+
// Update property mapping for nested object navigation
95+
if (!isLast)
96+
{
97+
currentPropertyMapping = PropertyMappingHelper.CreatePropertyMapping(current.Type);
98+
}
9299
}
93100

94101
return Result.Ok(((MemberExpression)current, nullChecks));
95102
}
96103

104+
private static Result<MemberExpression> ResolvePropertySegment(
105+
string segment,
106+
Expression parentExpression,
107+
Dictionary<string, string> propertyMapping)
108+
{
109+
if (!propertyMapping.TryGetValue(segment, out var propertyName))
110+
return Result.Fail($"Invalid property '{segment}' in path");
111+
112+
return Expression.Property(parentExpression, propertyName);
113+
}
114+
115+
private static Result<MemberExpression> ResolvePropertyPathForCollection(
116+
PropertyPath propertyPath,
117+
Expression baseExpression,
118+
Dictionary<string, string> propertyMapping)
119+
{
120+
var current = baseExpression;
121+
var currentPropertyMapping = propertyMapping;
122+
123+
for (int i = 0; i < propertyPath.Segments.Count; i++)
124+
{
125+
var segment = propertyPath.Segments[i];
126+
var propertyResult = ResolvePropertySegment(segment, current, currentPropertyMapping);
127+
if (propertyResult.IsFailed)
128+
return Result.Fail(propertyResult.Errors);
129+
130+
current = propertyResult.Value;
131+
132+
// Update property mapping for nested object navigation
133+
if (i < propertyPath.Segments.Count - 1)
134+
{
135+
currentPropertyMapping = PropertyMappingHelper.CreatePropertyMapping(current.Type);
136+
}
137+
}
138+
139+
return (MemberExpression)current;
140+
}
141+
97142
private static bool IsNullableReferenceType(Type type)
98143
{
99144
return !type.IsValueType || Nullable.GetUnderlyingType(type) != null;
@@ -363,15 +408,7 @@ private static Result<MemberExpression> ResolveCollectionProperty(QueryExpressio
363408
return Expression.Property(baseExpression, propertyName);
364409

365410
case PropertyPath propertyPath:
366-
var current = baseExpression;
367-
for (int i = 0; i < propertyPath.Segments.Count; i++)
368-
{
369-
var segment = propertyPath.Segments[i];
370-
if (!propertyMapping.TryGetValue(segment, out var segmentPropertyName))
371-
return Result.Fail($"Invalid property '{segment}' in lambda expression property path");
372-
current = Expression.Property(current, segmentPropertyName);
373-
}
374-
return (MemberExpression)current;
411+
return ResolvePropertyPathForCollection(propertyPath, baseExpression, propertyMapping);
375412

376413
default:
377414
return Result.Fail($"Unsupported property type in lambda expression: {property.GetType().Name}");

src/GoatQuery/tests/Filter/FilterTest.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,21 @@ public static IEnumerable<object[]> Parameters()
364364
new[] { TestData.Users["John"], TestData.Users["Apple"] }
365365
};
366366

367+
yield return new object[] {
368+
"company ne null ",
369+
new[] { TestData.Users["Jane"] }
370+
};
371+
372+
yield return new object[] {
373+
"company/name eq 'Acme Corp'",
374+
new[] { TestData.Users["Jane"] }
375+
};
376+
377+
yield return new object[] {
378+
"manager/manager/company/name eq 'My Test Company'",
379+
new[] { TestData.Users["Egg"] }
380+
};
381+
367382
yield return new object[] {
368383
"manager/balanceDecimal gt 100m",
369384
Array.Empty<User>()

src/GoatQuery/tests/TestData.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ public static class TestData
4848
AddressLine1 = "789 Pine Rd",
4949
City = new City { Name = "Seattle", Country = "USA" }
5050
}
51+
},
52+
Company = new Company
53+
{
54+
Name = "Acme Corp",
55+
Department = "Sales"
5156
}
5257
},
5358
["Apple"] = new User
@@ -154,6 +159,11 @@ public static class TestData
154159
DateOfBirth = DateTime.Parse("1983-04-21 00:00:00"),
155160
BalanceDecimal = 39.00m,
156161
IsEmailVerified = true
162+
},
163+
Company = new Company
164+
{
165+
Name = "My Test Company",
166+
Department = "Development"
157167
}
158168
}
159169
},

src/GoatQuery/tests/User.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public record User
1010
public float? BalanceFloat { get; set; }
1111
public DateTime? DateOfBirth { get; set; }
1212
public bool IsEmailVerified { get; set; }
13+
public Company? Company { get; set; }
1314
public User? Manager { get; set; }
1415
public IEnumerable<Address> Addresses { get; set; } = Array.Empty<Address>();
1516
public IEnumerable<string> Tags { get; set; } = Array.Empty<string>();
@@ -31,4 +32,10 @@ public record City
3132
{
3233
public string Name { get; set; } = string.Empty;
3334
public string Country { get; set; } = string.Empty;
35+
}
36+
37+
public record Company
38+
{
39+
public string Name { get; set; } = string.Empty;
40+
public string Department { get; set; } = string.Empty;
3441
}

0 commit comments

Comments
 (0)