I have a question relating to nested results using ServiceStack's Autoquery.
Specifically,
Firstly,
I have two classes. A Parent class with a referenced list of children, as shown below:
[Alias("view_parent")]
public class ParentView
{
public int Id { get; set; }
public string ParentName {get;set;}
[Reference]
public List<ChildView> Children {get;set;}
}
[Alias("view_children")]
public class ChildView
{
[References(typeof (ParentView))]
public int ParentId { get; set; }
public string ChildName {get;set;}
}
Secondly, I have an Autoquery class as follows:
[Route("/parents", "GET")]
public class GetParents : QueryBase<ParentView>
{
}
Given the above,
Does AutoQuery support searching within the List of children from the ParentView?
e.g. the API query
/parents?ChildName=Tom
does not seem filter the results. Does AutoQuery automatically support searching within a List?
Thanks & by the way ServiceStack is pretty awesome!
AutoQuery doesn't include any child references as part of the query. You'll need to explicitly Join tables you want included in the executed query.
Related
I've been reviewing servicestack and the documentation. In regards to autoquery documentation the pre-autoquery and post auto query design is shown below. Where the DTO does not include the parameter "BookedAfter". It is my understanding in a non-Auto Query scenario that a Get would provide these parameters for the obvious query input options. For Auto Query I have a few questions. First, it would appear to me that if you only provide specific parameters (instead of leaving it wide open) that only those would be allowed for filtering (assuming DTO specific fields)? Is this out of the box or would one need to override the Auto Query implementation? Similarly with below, the code utilized a custom "BookedAfter" parameter. Would one override the implementation, map the more specific parameter wording to a DTO field query scenario? And what would it take to allow additional querying capabilities that came out of the box? I have not been able to find an example from documentation or the community.
[Route("/bookings/search")]
public class SeachBookings : IReturn<SeachBookingsResponse>
{
public DateTime BookedAfter { get; set; }
}
[Route("/bookings/search")]
public class SeachBookings : QueryDb<Booking>
{
public DateTime BookedAfter { get; set; }
}
// Types
public class Booking
{
public int Id { get; set; }
public int ShiftId { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public int Limit { get; set; }
}
Auto Query parameters simply match the rules in the configured Implicit Conventions, it's irrelevant if the property is defined on the DTO or not unless you restrict it with EnableUntypedQueries=false in which case it will only look at the conventions from explicit DTO properties.
Your BookedAfter matches the Implicit Convention:
{"%After%", GreaterThanFormat},
You're not limited to the pre-configured conventions and can add/remove your own rules.
So, I'm working with ServiceStack and love what it offers. We've come to a point where I'm needing to implement a queryable data API... prior to my coming to this project, a half backed OData implementation was done. I'd rather not try and weed through that to make it work.
Which brings me to AutoQuery. I'd like to try it with our SQL Server database. I'm looking at the examples at http://docs.servicestack.net/autoquery-rdbms - but I cannot for the life of me get this to work. Is there something I'm missing here?
I'm using ORMLite to query SQL, and my integration tests I've written show it to be working as I would expect. I have registered the OrmLiteConnectionFactory in the container, as well as my repository which uses it by way of dependency injection.
Specific to code so far, I have a type, and a message that is based on QueryDb:
public class Detail
{
public string Div { get; set; }
public string Reg { get; set; }
}
[Route("/report/detail")]
public class DetailQuery : QueryDb<Detail>
{
public string[] Div { get; set; }
public string[] Reg { get; set; }
}
The message, DetailQuery, is used by my service:
public class ReportService : Service
{
public object Get(DetailQuery dq)
{
// not sure what to put here?
}
}
With all of that, I am able to see the AutoQuery service instance in the admin interface. When I play with the query interface, I hit my service endpoint, and I see the data I expect - filter values in the 'Div' and 'Reg' collections. What am I missing for this to 'just work' here? I have done plenty in ServiceStack accessing my repositories from the Service itself, but I'm trying to gain some insight into what AutoQuery brings to the table here. I have yet to see a 'straight forward' example of how this works... or am I looking for a pot of gold that just isn't there?
AutoQuery works with just the Request DTO i.e. it doesn't need any Service implementation, so your query:
[Route("/report/detail")]
public class DetailQuery : QueryDb<Detail>
{
public string[] Div { get; set; }
public string[] Reg { get; set; }
}
When called from /report/detail will query the Detail RDBMS Table. But your properties here either need to match a column on the Detail table (e.g. Div or Reg) in order to have an exact match (default), however exact matches aren't normally done with arrays they're done with scalar values like a string, e.g:
public string Div { get; set; }
public string Reg { get; set; }
If you're querying a collection you'd be instead making an IN Query where the values would contain list of values, in which case they're normally pluralized:
public string[] Divs { get; set; }
public string[] Regs { get; set; }
and can be called with:
/report/detail?Divs=A,B&Regs=C,D
Which will perform a query similar to:
SELECT * FROM Detail WHERE Div IN ('A','B') AND Rev IN ('C','D')
If that's not the behavior you want it needs to match an implicit convention, e.g:
public string[] DivBetween { get; set; }
Which will then query:
SELECT * FROM Detail WHERE Div BETWEEN 'A' AND 'B'
If you wanted to you could override the AutoQuery service with a custom implementation, e.g:
public class MyQueryServices : Service
{
public IAutoQueryDb AutoQuery { get; set; }
//Override with custom implementation
public object Any(DetailQuery query)
{
var q = AutoQuery.CreateQuery(query, base.Request);
return AutoQuery.Execute(request, q);
}
}
But you'd only need to do that when you want to customize the default behavior, e.g. add an extra filter to the populated SqlExpression.
My domain objects are like:
public class MainType {
public int Id {get;set;}
public string Name {get;set;}
public List<TypeA> A_List {get;set;}
public List<TypeB> B_List {get;set;}
... other properties
}
public class TypeA {
public int Id {get;set;}
public string Name {get;set;}
... other properties
}
public class TypeAMapping {
public int TypeAId {get;set;}
public int MainTypeId {get;set;}
public int DisplayOrder {get;set;}
}
public class TypeB {
public int Id {get;set;}
public string Name {get;set;}
... other properties
}
public class TypeBMapping {
public int TypeBId {get;set;}
public int MainTypeId {get;set;}
public int DisplayOrder {get;set;}
}
Azure Search index documents does not support for complex types so I need to flatten these all classes into a model as described here.
So, I created a class like this one:
public class MainTypeDocumentModel {
public int Id {get;set;}
public string Name {get;set;}
public List<string> A_Id_List {get;set;}
public List<string> A_Name_List {get;set;}
public List<string> A_DisplayOrder_List {get;set;}
public List<string> B_Id_List {get;set;}
public List<string> B_Name_List {get;set;}
public List<string> B_DisplayOrder_List {get;set;}
... other properties
}
The problem is I also need to process DisplayOrder property of the mapping classes. Which the documentation does not cover.
I can create queries to search MainTypeDocumentModel filtered by A_Id_List and/or B_Id_List. But I need to sort the documents (or score higher) with the values in X_DisplayOrder_List property of the documents.
I checked the Scoring Profile docs from Microsoft but couldn't figure out how to implement for this scenario.
It sounds like what you want is the equivalent of correlated sub-queries on the nested A's and B's. Unfortunately this is not currently possible in Azure Search since it requires built-in support for complex types. This is on our radar but there is no ETA at this time.
In the meantime, you can consider other ways of modeling your domain types as Azure Search indexes. One option is full denormalization; Have an A index and a B index, and repeat Id and Name for each combination with As and Bs. Another option is full normalization; Have separate indexes for MainType, A, B, and the relations between them, and do the "joins" on the client side. There are tradeoffs involved depending on your query patterns and update frequency. This thread on the MSDN forums covers these options in a bit more detail.
Does Entity Framework provide an option to retrieve child objects that are only populated with fields that are foreign keys to the parent object?
Sample code might illustrate this better.
Assuming you have the following POCO classes...
public abstract class Base
{
public Guid Id { get; set; }
}
public class User : Base
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Photo : Base
{
public string Description { get; set; }
public User UploadedBy { get; set; }
}
... and assuming you've configured a DbContext correctly, how do you query for a list of all Photos including the UploadedBy object, but where that UploadedBy object only contains the Id property?
I know I can do this...
return await _dbContext.Photos.Include(p => p.UploadedBy).ToListAsync();
... but that returns the entire User object.
I'd like to do something like this...
return await _dbContext.Photos.Include(p => p.UploadedBy.Id).ToListAsync();
... to indicate that I only want the Id property back.
If we could chain those includes we would be able to pick each property on the child object that we want returned.
Or even better, I'd love to be able to configure a setting at a more global level that would make it so that anytime I ask for Photos, give me all members of photos, even child objects, but only populate their foreign keys and nothing more.
The last request is less important though because I could just create the following extension method for each POCO object...
public static IQueryable<Photo> IncludeForigenKeys(this PhotoAlbumDbContext context){
return context.Photos
.Include(photo => photo.UploadedBy.Id);
}
As far as I understand there is no way to partially load a Navigation Property.
However for foreign keys the standard way of accessing these without loading the Nav property is to include the actual key in your model. Eg:
public class Photo : Base
{
public string Description { get; set; }
public int UploadedById { get; set; }
public User UploadedBy { get; set; }
}
This id will be populated even if you don't actually load the whole navigation property.
In the case where you load both you can update either the value on the local or remote end of the nav property and that update will be persisted to the database on save. In my experience EF is very clever around this. The only scenario where it becomes a little more tricky is in unit tests where EF is not maintaining this state.
I'd like to be able to project/map properties from either of 2 child classes.
Imagine a parent class that has 2 child classes, either of 1 which points to a child class. Each child class must map to properties on the model class.
However, if child class 1 is null then it shouldn't map across to the model it should map class 2 properties instead.
It's like what I want to do here except do it across a whole class of properties rather than 1 property:
Conditonal projection
I was hoping I don't have to create a CustomResolver for each class property and whether there is a better way of achieving this. Imagine that the child classes have 10 identical properties each, all I want to do is switch the map depending on which child class is populated.
public class Message
{
public string Comment { get; set; }
public Inbound? InboundMessage { get; set; }
public Outbound? OutboundMessage { get; set; }
}
public class Inbound
{
public string Body { get; set; }
// 10 properties...
}
public class Outbound
{
public string Body { get; set; }
// 10 properties...
}
public class MessageModel
{
public string Comment { get; set; }
public string Body { get; set; }
// 10 properties....
}
Surprised no-one had an answer to this so answered myself by the time I worked out what to do.
If you have the need to conditionally map properties depending on whether a child class is populated or not, or any other logic for the whole class for that matter, then you can use a TypeConverter.
Inside the TypeConverter, you test the source parameter in the ConvertCore function, and then call Mapper.Map on the correct source to pass the child class properties out to the model.
The return from mapper can be then returned back out which will be the destination.
I never realised that you can still call Mapper.Map() even in the TypeConverter.