Autquery not including nested result when using a response DTO - servicestack

Let's say you have these models:
public class Blog
{
[PrimaryKey]
[AutoIncrement]
public int Id { get; set; }
public string Url { get; set; }
public string PrivateField { get; set; }
[Reference]
public List<BlogToBlogCategory> BlogToBlogCategories { get; set; }
}
public class BlogResponse
{
public int Id { get; set; }
public string Url { get; set; }
public List<BlogToBlogCategory> BlogToBlogCategories { get; set; }
}
And this request:
public class BlogsLookUpRequest : QueryDb<Blog, BlogResponse>
{
}
The return value will have BlogToBlogCategories as null, but this request:
public class BlogsLookUpRequest : QueryDb<Blog>
{
}
Will have BlogToBlogCategories populated. I can manually create the query response like so with custom implementation:
var q = _autoQuery.CreateQuery(request, Request.GetRequestParams(), base.Request);
var results = _autoQuery.Execute(request,q, base.Request);
return new QueryResponse<ResponseBlog>()
{
Results = results.Results.ConvertTo<List<ResponseBlog>>(),
Offset = request.Skip,
Total = results.Total
};
Then it will have the nested results. If I decorate the collection with [Reference] then it is trying to find foreign key on non-existant BlogResponse table.
Why are referenced results removed when specifying a return model with AutoQuery? Is there a way to mark it up so it works?

The POCO Reference Types is used to populate Data Models not adhoc Response DTOs.
In this case it's trying to resolve references on a non-existent table, you can specify which table the DTO maps to with [Alias] attribute, e.g:
[Alias(nameof(Blog))]
public class BlogResponse
{
public int Id { get; set; }
public string Url { get; set; }
public List<BlogToBlogCategory> BlogToBlogCategories { get; set; }
}

Related

Autoquery servicestack ILeftJoin issue with table.id column

I have definition of autoquery dto defined as below:
[Route("/project/{ProjectId}/contracts/{ContractId}/items")]
public class QueryContractItem : QueryDb<ContractItem, ContractItemResponse>,
ILeftJoin<ContractItem, ContractItemEstimateItem>,
ILeftJoin<ContractItemEstimateItem, EstimateItem>,
ILeftJoin<ContractItemEstimateItem, ContractItemEstimateItemComponent>,
ILeftJoin<EstimateItem, EstimateComponent>,
ILeftJoin<EstimateItem, EstimateGroup>
{
public int ProjectId { get; set; }
public int ContractId { get; set; }
}
public class ContractItemResponse
{
// ...
public int ContractItemEstimateItemId { get; set; } // WRONGLY taken from ContractItemEstimateItemComponent.ContractItemEstimateItemId - PROBLEM
public int ContractItemEstimateItemEstimateItemId { get; set; } //value is taken correctly from ContractItemEstimateItem - CORRECT
public int ContractItemEstimateItemContractItemId { get; set; } //value is taken correctly from ContractItemEstimateItem - CORRECT
}
Not sure why during query execution the value of ContractItemEstimateItemId is taken from ContractItemEstimateItemComponent.ContractItemEstimateItemId (the value is not yet there).
Expected is that query will take a value for field ContractItemEstimateItemId from ContractItemEstimateItem.Id

Autmapper nested mapping

I have the following main class:
public class ResearchOutcome
{
public ResearchOutcomeCategory ResearchOutcomeCategory { get; set; }
public string? UniqueIdentifier { get; set; }
}
And the category class is:
public class ResearchOutcomeCategory
{
public int Id { get; set; }
public string Name { get; set; }
public string? Description { get; set; }
}
The View models for above classes are:
public class ResearchOutcomeDetailVm : IMapFrom<ResearchOutcome>
{
public int Id { get; set; }
public virtual ResearchOutcomeCategoryDetailVm ResearchOutcomeCategory { get; set; }
}
public class ResearchOutcomeCategoryDetailVm : IMapFrom<ResearchOutcomeCategory>
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Now, I have used the following mapping profile:
// First this one
profile.CreateMap<ResearchOutcomeCategory, ResearchOutcomeCategoryDetailVm>();
profile.CreateMap<ResearchOutcome, ResearchOutcomeDetailVm>();
//Then I tried this one
profile.CreateMap<ResearchOutcome, ResearchOutcomeDetailVm>()
.ForMember(o => o.ResearchOutcomeCategory,
cat => cat.MapFrom( o => o.ResearchOutcomeCategory));
But the ResearchOutcomeCategory is always null. Any help would be appreciated.
After digging more, I identified that I was not "Including" the relevant item in the query, hence, the view model was always empty. Pretty dumb on my part :D
Regarding the mapping, if the properties (even complex ones) have the same names, then the mapper will map them automatically. So simply this line worked
profile.CreateMap<ResearchOutcomeCategory, ResearchOutcomeCategoryDetailVm>();
Hope it helps someone

PATCH in ServiceStack

I am trying to patch a object with the following code.
public object Patch(EditBlog request)
{
using (var db = _db.Open())
{
try
{
request.DateUpdated = DateTime.Now;
Db.Update<Blog>(request, x => x.Id == request.Id);
return new BlogResponse { Blog = Blog = Db.Select<Blog>(X=>X.Id == request.Id).SingleOrDefault()};
}
catch (Exception e)
{
return HttpError.Conflict("Something went wrong");
}
}
}
In Postman, I am calling the function like this "api/blog/1?=Title=Test1&Summary=Test&UserId=1".
When debugging I can see that those values has been assigned to the request.
During the Update it throws: "Cannot update identity column 'Id'"
My model looks like this
public class Blog
{
[AutoIncrement]
public int Id { get; set; }
public IUserAuth User { get; set; }
[Required]
public int UserId { get; set; }
[Required]
public string Title { get; set; }
public string Summary { get; set; }
public string CompleteText { get; set; }
[Required]
public DateTime DateAdded { get; set; }
public DateTime DateUpdated { get; set; }
}
And the EditBlog DTO looks like this:
[Route("/api/blog/{id}", "PATCH")]
public class EditBlog : IReturn<BlogResponse>
{
public int Id { get; set; }
public IUserAuth User { get; set; }
public int UserId { get; set; }
public string Title { get; set; }
public string Summary { get; set; }
public string CompleteText { get; set; }
public DateTime DateUpdated { get; set; }
}
The error message "Cannot update identity column 'Id'" does not exist anywhere in ServiceStack.OrmLite, it could be an error returned by the RDBMS when you're trying to update the Primary Key which OrmLite wouldn't do when updating a Model annotated with a Primary Key like your Blog class has with its annotated [AutoIncrement] Id Primary Key.
The error is within your Db.Up<T> method that's performing the update, which is not an OrmLite API, so it's likely your own custom extension method or an alternative library.
I would implement a PATCH Request in OrmLite with something like:
var blog = request.ConvertTo<Blog>();
blog.DateUpdated = DateTime.Now;
Db.UpdateNonDefaults(blog);
i.e. using OrmLite's UpdateNonDefaults API to only update non default fields and updating using the Blog Table POCO not the EditBlog Request DTO.
Also you should use the Single APIs when fetching a single record, e.g:
Blog = Db.SingleById<Blog>(request.Id)
or
Blog = Db.Single<Blog>(x => x.Id == request.Id)
Instead of:
Blog = Db.Select<Blog>(X=>X.Id == request.Id).SingleOrDefault()

Conversion failed,when using AutoQuery in ServiceStack

I have the following AutoQuery function.
[Route("/cars/search")]
public class SearchCars : QueryDb<Car, CarDto>
{
public List<int> EquipmentIds { get; set; }
public List<int> ManufacturerIds { get; set; }
public List<int> ColourIds { get; set; }
}
The function works, when I do the following:
Cars/Search?ColourIds=1&format=json
Cars/Search?ManufacturerIds=1&format=json
but when I try to use
Cars/Search?EquipmentIds=1&format=json
I get "Conversion failed when converting the varchar value '[1]' to data type int.".
The difference between these fields is that Car object can have multiple EquipmentIds, but only one ColourId and ManufacturerId.
public class Car
{
[AutoIncrement]
public int Id { get; set; }
public Colour Colour { get; set; }
[Required]
public int ColourId { get; set; }
public Manufacturer Manufacturer { get; set; }
[Required]
public int ManufacturerId { get; set; }
[Required]
public List<Equipment> Equipment { get; set; }
[Required]
public List<int> EquipmentId { get; set; }
}
Do I have to define for which attribute the different parameters should be assigned too?
AutoQuery works by constructing an RDBMS query based on implicit conventions which is used to construct an SQL query that runs on the RDBMS.
Complex Types in OrmLite data models are blobbed by default which means they can't be queried in the RDBMS with SQL, so you wont be able to query it with AutoQuery.
You could create a hybrid Custom AutoQuery Implementation where you can apply any custom logic to filter the results of the AutoQuery results, something like...
public class MyQueryServices : Service
{
public IAutoQueryDb AutoQuery { get; set; }
//Override with custom implementation
public object Any(SearchCars query)
{
var equipmentIds = query.EquipmentIds;
query.EquipmentIds = null;
var q = AutoQuery.CreateQuery(query, base.Request);
var response = AutoQuery.Execute(query, q);
if (equipmentIds != null)
response.Results.RemoveAll(x => x.EquipmentId...);
return response.
}
}

Automapper projection results in empty collection for nested Dto

I have a .Net Core 2 webapi in which I am using automapper to map to Dtos. Everything works fine, except I am seeing an unexpected behaviour when I map an object to a Dto, and where the Dto also contains mappings for a collection. E.g
CreateMap<Order, OrderDto>();
CreateMap<Product, ProductDto>();
Where classes are like this
public partial class Order
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products{ get; set; }
public int ProductCount {return Products.Count;}
}
public partial class Product
{
public int Id { get; set; }
public int OrderId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
The following works as expected;
The class is mapped, and the ProjectCount is correct in the Dto
public partial class OrderDto
{
public int Id { get; set; }
public virtual ICollection<Product> Products{ get; set; }
public int ProductCount{ get; set; }
}
_context.Orders.Include<>(Products).ProjectTo<>(OrderDto)
But doing the following, the productcount is always zero.
E.g. if I do this;
public partial class OrderDto
{
public int Id { get; set; }
public virtual ICollection<ProductDto> Products{ get; set; }
public int ProductCount{ get; set; }
}
public partial class ProductDto
{
public int Id { get; set; }
public int OrderId { get; set; }
public string Name { get; set; }
}
_context.Orders.Include<>(Products).ProjectTo<>(OrderDto)
Why does this happen, and how can I ensure that it doesnt? This is a real world example where I need a property which references the collection - and I need it in both the base and the Dto. I can do the following which works fine, but it doesnt appear that this should be how it works...
public partial class OrderDto
{
public int Id { get; set; }
public virtual ICollection<ProductDto> Products{ get; set; }
public int ProductCount {return Products.Count;}
}
public partial class ProductDto
{
public int Id { get; set; }
public string Name { get; set; }
}
_context.Orders.Include<>(Products).ProjectTo<>(OrderDto)
I profiled the SQL and found that Automapper changes the way the query is formed. Without the nested projection, two queries are made;
[Queries are more complex than this and use joins, but you get the idea]
Select Id from orders
Select Id,Name from products where productid in [select id from orders ]
With the nested projection, are executed for each nested Dto
Select Id from orders
Select Id,Name from products where id=1
Select Id,Name from products where id=2

Resources