Handling query string parameters without EF Core
See original GitHub issueFrom https://gitter.im/json-api-dotnet-core/Lobby?at=634e9e31a3ccb16cbe13f782:
Alexandre DOMS @Alexandre.D-IdemSante_gitlab Oct 18 14:38
Hello everyone, I am trying to use JsonApiDotNetCore without EntityFramework, on an already existing database (and on which I cannot change the structure). I have tables that don’t have a unique id, but are composed of the parent key + a child key. What is the best practice to build my resources? Can I define a “Tid” with a multi-value key?
Here is a simple example in a database
Persons : ID NAME 1 Ryder 2 Joe 3 William
Dogs PARENTID | ID | DOG 1 | 1 | Rubble 1 | 2 | Marshall 2 | 1 | Rocky
How would you build the resource classes?
Bart Koelman @bkoelman Oct 18 18:48
If you’d use EF Core, we have a sample that shows how to handle composite PKs at https://github.com/json-api-dotnet/JsonApiDotNetCore/tree/master/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys. But if you don’t use EF Core, then you’re responsible for implementing logic to query the database yourself, so you can do it in any way you want.
Alexandre DOMS @Alexandre.D-IdemSante_gitlab Oct 19 11:22
Hi Bart and thanks for the quick response! I had also thought of an implementation based on key concatenation, so I’ll go with that. I have another question, again because of the fact that I’m not using entity, how do I intercept the parameters passed by filters and sorts? Is it in JsonApiResourceDefinition? Because OnApplySort and OnApplyFilter seem not to be called, while OnApplySparseFieldSet is called when i use expected query args.
Bart Koelman @bkoelman [Oct 19 13:16]
See json-api-dotnet/JsonApiDotNetCore#1181, which shows how to access parsed query string parameters. When not using EF Core, you’ll need to make the appropriate calls into resource definitions yourself (via injected IResourceDefinitionAccessor). The reason you’re seeing OnApplySparseFieldSet being called is because the response serializer throws out any additional fields that were retrieved from database, but should not be returned. Normally it is called twice, the first time allowing to fetch additional fields from DB, typically if there are calculated properties whose value depends on another property that isn’t requested.
Alexandre DOMS @Alexandre.D-IdemSante_gitlab Oct 20 00:55
Thank you! My mistake was to extract the expressions in the service constructor, but it seems that the analysis is not yet done at that time. By doing the expression extraction in each service command, I get the data passed in the url. I need now to create my own base service class, have to include the expression parsing + definition event calls + calls to my custom repository. Great job anyway, great framework!
Bart Koelman @bkoelman Oct 20 03:54
Thanks!
Alexandre DOMS @Alexandre.D-IdemSante_gitlab Oct 22 00:38
Hello, I have a new problem, I have integrated a property exposed with a relation [HasMany] in my entity. When I try to load via http://route/1?include=myproperty the json flow response returns “included”: [] while my object is correctly instantiated on the c# side, I can’t understand what’s wrong, is it a problem with the serialization options? The uri seems to be well interpreted because if I fill a bad value in myproperty, I have an error. I have the problem with a [HasOne] attribute too. Any suggestions?
Bart Koelman @bkoelman Oct 22 01:47
I would guess you didn’t populate the relationship property with objects retrieved from the database, but it’s hard to say without more info. Can you create an issue with minimal repro steps?
Bart Koelman @bkoelman Oct 22 01:50
Alternatively, you can step into the source code from our NuGet while debugging, see the next link on how to use: https://devblogs.microsoft.com/dotnet/improving-debug-time-productivity-with-source-link/
Alexandre DOMS @Alexandre.D-IdemSante_gitlab Oct 23 10:06
My entities :
public class Person : Identifiable<int> { [Attr] public string Name { get; set; } [Attr] public int Age { get; set; } [HasMany] ICollection<Dog> Dogs { get; set; } } public class Dog : Identifiable<string> { [HasMany] public Person Owner { get; set; } [Attr] public string Name { get; set; } } public class Program { var builder = WebApplication.CreateBuilder(args); builder.Services.AddJsonApi(opt => { opt.SerializerOptions.WriteIndented = true; opt.RelationshipLinks = JsonApiDotNetCore.Resources.Annotations.LinkTypes.All; opt.DefaultAttrCapabilities = JsonApiDotNetCore.Resources.Annotations.AttrCapabilities.All; opt.ResourceLinks = JsonApiDotNetCore.Resources.Annotations.LinkTypes.All; opt.TopLevelLinks= JsonApiDotNetCore.Resources.Annotations.LinkTypes.All; }, discovery => { discovery.AddCurrentAssembly(); }); builder.Services.AddScoped<IResourceService<Person, int>, PersonService>(); builder.Services.AddScoped<IResourceService<Dog, string>, DogService>(); builder.Services.AddScoped<IDogRepository, DogRepository>(); builder.Services.AddScoped<IPersonRepository, PersonRepository>(); var app = builder.Build(); app.UseRouting(); app.UseJsonApi(); app.MapControllers(); app.Run(); } public class PersonService : IResourceService<Person, int> { IPersonRepository repo; public PersonService(IPersonRepository repo) { repo = _repo; } public Task<Person> GetAsync(int id, CancellationToken cancellationToken) { return Task.Run(() => { return repo.GetByID(id); }); } public Task<IReadOnlyCollection<Person>> GetAsync(CancellationToken cancellationToken) { return Task.Run(() => { return repo.GetAll(); }); } // ... } public class DogService : IResourceService<Dog, string> { IDogRepository _repo; public DogService(IDogRepository repo) { _repo = repo; } public Task<Dog> GetAsync(string id, CancellationToken cancellationToken) { return Task.Run(() => { return repo.GetById(id); }); } public Task<IReadOnlyCollection<Dog>> GetAsync(CancellationToken cancellationToken) { return Task.Run(() => { return _repo.GetAll(); }); } // ... }
Alexandre DOMS @Alexandre.D-IdemSante_gitlab Oct 23 10:12
I tried to debug it, when calling http://route/Dogs/1?include=owner, but i saw nothing, i think it’s in the ResponseModelAdapter, when Convert function is called, especially when document.Included = GetIncluded(rootNode); is called.
Issue Analytics
- State:
- Created a year ago
- Comments:7 (3 by maintainers)
Top GitHub Comments
Thanks, I finally figured it out on my end too: I find the instance of IIncludeQueryStringParameterReader and extracte the IncludeExpression from it. Just add it to the IEvaluatedIncludeCache instance and it works.
IncludeExpression contains a set of subtrees. You can build your own (example here) or just feed a string to IncludeParser (example here).
In general, many questions you can find the answer for by searching for usages in our codebase, or debugging a test to see how things look like. We have excellent coverage for all kinds of things, just a small subset is in the documentation website.