Can AutoQuery return a single item (not list) - servicestack

When I add a type to AutoQuery, with:
[Route("/templates")]
public class SearchTemplates : QueryDb<Template>
{
public int? Id { get; set; }
public string Name { get; set; }
}
then I can query this object by Id or Name (or whatever other attributes I would add, that the POCO Template has). However it always returns list of items.
It's very useful to be able to GET a single item (not a search result).
This is how I do it:
[Route("/template/{Id}","GET")]
public class SingleTemplate : IReturn<Template>
{
public int Id { get; set; }
}
public Template Get(SingleTemplate request)
{
return Db.LoadSingleById<Template>(request.Id);
}
With all the new AutoQuery and AutoCRUD, it seems to me that the "return a single item by its URL" could also be automatic?

No, All AutoQuery QueryDb<T> services return the same fixed QueryResponse Response DTO as per its Service Contract, i.e:
public abstract class QueryDb<T>
: QueryBase, IQueryDb<T>, IReturn<QueryResponse<T>> { }
public abstract class QueryDb<From, Into>
: QueryBase, IQueryDb<From, Into>, IReturn<QueryResponse<Into>> { }
public class QueryResponse<T> : IQueryResponse
{
public virtual int Offset { get; set; }
public virtual int Total { get; set; }
public virtual List<T> Results { get; set; }
public virtual Dictionary<string, string> Meta { get; set; }
public virtual ResponseStatus ResponseStatus { get; set; }
}
A single result would still populate the Results property, so the JSON wire format would look like:
{ "results":[ {/*Template*/} ] }
You could create your own Custom AutoQuery Implementation that utilizes AutoQuery's IAutoQueryDb API to return your own custom populated DTO but then your Request DTO should NOT inherit from QueryDb<T> as not returning a QueryResponse<T> would break the explicit Service contract of the Request DTO (and all clients expecting it), i.e. you would instead just create a normal ServiceStack Service returning your own custom Response Type.

Related

Automapper - flattening of object property

let's say I have
public class EFObject
{
public int Id { get; set; }
public int NavId { get; set; }
public NavObject Nav { get; set; }
}
public class DTOObject
{
public int Id { get; set; }
public int NavId { get; set; }
public string NavName { get; set; }
}
My expectation was high, and I thought to my self the built-in flattening should handle this, so my mapping is very simple
CreateMap<DTOObject, EFObject>().ReverseMap();
Unfortunately, converting DTOObject to EFObject does not work as expected because EFObject.Nav is null. Since I used the name NavId and NavName I would expect it to create a new NavObject and set the Nav.Id and Nav.Name accordingly.
My Problem : Is there a feature in Automapper that will allow me to achieve the intended result without having to manually write a rule to create an NavObject when mapping the Nav property?.
Unflattening is only configured for ReverseMap. If you want unflattening, you must configure Entity -> Dto then call ReverseMap to create an unflattening type map configuration from the Dto -> Entity.
as noted by Automapper documentation here

DDD/CQRS, Entity has access to Query, Command?

public class PageRoleService
{
public void SetRoles(Page page, User activeUser)
{
var rb = page.Project.ProjectType.GetRoleFor(activeUser.UserType);
page.RolesForPage.Add(activeUser, rb);
var managers = GetAllManagersOf(activeUser);
foreach (var m in managers)
{
page.RolesForPage.Add(m, rb);
}
}
}
public class Project : Entity
{
public ProjectType ProjectType { get; set; }
public IList<Page> Pages { get; set; }
}
public class Page : Entity
{
public string Name { get; set; }
public Project Project { get; set; }
public IDictionary<User, RoleBehaviour> RolesForPage { get; set; }
}
public class ProjectType : Entity
{
public IQueryProcessor QueryProcessor { get; set; }
public IList<RoleBehaviour> RoleBehaviours { get; set; }
public RoleBehaviour GetRoleFor(USerType userType)
{
var behaviour = return QueryProcessor.Execute(new GetRolesByUserAndProjectTypeQuery() {
ProjectType = this,
UserType = userType
});
// Filter behaviour attributes for project type properties, business rules, etc...
// FilterBehaviour(behaviour);
return behaviour;
}
}
public class GetRolesByUserAndProjectTypeQuery
{
public UserType UserType { get; set; }
public ProjectType ProjectType { get; set; }
}
public class GetRolesByUserAndProjectTypeQueryHandler
{
public Db Db { get; set; }
public RoleBehaviour Execute(GetRolesByUserAndProjectTypeQuery query)
{
return Db.FirstOrDefault(r => r.UserType == query.UserType && r.ProjectType == query.projectType);
}
}
public class RoleBehaviour : Entity
{
public Role ROleForArea1 { get; set; }
public Role ROleForArea2 { get; set; }
public UserType UserType { get; set; }
public ProjectType ProjectType { get; set; }
public IDictionary<string, string> Attributes { get; set; }
}
public enum UserType
{
A,
B,
C,
D
}
public class Role : Entity
{
public IList<string> Permissions { get; set; }
}
I don't use repository, no need data abstraction, I use CQRS for crud operations. (CreateProjectCommand, GetRolesByUserAndProjectTypeQuery, etc..)
Users related a lot of project and page. Users have more than role for each Page Entity and is dynamically created when user (client) request to fetch All projects page or single page item.
My Page Role Service determinates page roles for active user and its managers. My MVC Controller use PageRoleService.
PageRoleService is Application Service or Domain Service or .....?
QueryProcessor in Entity (ProjectType) is invalid approach? How can handle this/their problems without lazy or eager loading?
RoleBehaviour is Entity or Value Object?
PageRoleService is a service or business logic in domain?
I know that I'm some years later, but:
I would put away the base class Entity, because it looks that this are just Dtos returned by the queryhandler (infact GetRolesByUserAndProjectTypeQueryHandler.Execute returns a RoleBehaviour).
Given this, I think that:
PageRoleService is a simple service that completes a Dto, hence it looks a kind of factory
Given that ProjectType here has two different roles (a Dto and Entity, and this is against CQRS), if:
it's a Dto, then use a service/factory/ORM to load extra data on it
it's an Entity, try to load all the data that's needed by it. This because there're great changes that you'll need it on the way to execute your command (great explanation about DDD and entities).
The object has it's own identity? Has it an Id that, even if things will change, remains the same? Looking at it, it looks just a Dto, with nothing really interesting (at business level).
see 1.

Breeze doesn't expand TPH entities correctly

Breeze doesn't expand TPH entities correctly.
When using expand in breeze if you are using TPH expand will only work for the first entity, the others properties will be null. If I change the entity not to use inheritances it works fine. I've also tested returning each entity separately in an expand query that also worked fine.
//client side code
var getResidentById = function (id, obs) {
var query = EntityQuery.from('Residents')
.where('id', '==', id)
.expand('user, currentUnit, leases, leases.unit, leases.leaseStatus');
return manager.executeQuery(query).then(function (data) {
if (obs) {
obs(data.results[0])
}
}, queryFailed);
};
//Controler Endpoint
[HttpGet]
public IQueryable<Resident>
{
return _context.Context.UserDetails.OfType<Resident>();
}
//Model
public class UserDetail : EntityBase<int>, IArchivable, IHasPhoto, IDeactivatableEntity, IUpdatable
{
public bool IsArchived { get; set; }
public int LastUpdatedById { get; set; }
public UserProfile LastUpdatedBy { get; set; }
public DateTimeOffset LastUpdatedDate { get; set; }
public string PhotoUri { get; set; }
public bool IsInactive { get; set; }
}
public abstract class UserBelongsToApartmentComplex : UserDetail, IBelongsToApartmentComplex
{
public int ApartmentComplexId { get; set; }
public virtual ApartmentComplex ApartmentComplex { get; set; }
public virtual bool IsInSameComplexAs(IRelatedToApartmentComplex otherEntity)
{
return ApartmentComplexId == otherEntity.ApartmentComplexId;
}
}
public class Staff : UserBelongsToApartmentComplex
{
public string Title { get; set; }
}
public class Admin : UserDetail
{
public string AccessLevel { get; set; }
}
public class Resident : UserBelongsToApartmentComplex
{
public string Pets { get; set; }
public bool HasInsurance { get; set; }
public virtual IList<Lease> Leases { get; set; }
public int? CurrentUnitId { get; set; }
public virtual Unit CurrentUnit { get; set; }
public Resident()
{
Leases = new List<Lease>();
}
}
//response data from sever from endpoint public IQueryable Residents()
[{"$id":"1","$type":"RadiusBlue.Core.Models.Resident, RadiusBlue.Core","Pets":"Sadie, a westie","HasInsurance":false,"Leases":[{"$id":"2","$type":"RadiusBlue.Core.Models.Lease, RadiusBlue.Core","Start":"2012-05-23T00:00:00.000","End":"2013-05-23T00:00:00.000","UnitId":2,"Unit":{"$id":"3","$type":"RadiusBlue.Core.Models.Unit, RadiusBlue.Core","Building":"B","Floor":2,"ModelName":"Tera","RentAmount":2500.00,"NumberOfBeds":1,"NumberOfBaths":3,"UnitName":"102A","IsInactive":true,"Inhabitants":[],"ApartmentComplexId":1,"ApartmentComplex":{"$id":"4","$type":"RadiusBlue.Core.Models.ApartmentComplex, RadiusBlue.Core","Name":"The Stratford","StreetAddress":"100 S Park Ave","City":"Winter Park","StateId":10,"ZipCode":"32792","PropertyManagementCompanyId":1,"IsInactive":false,"TimeZoneId":"Eastern Standard Time","TimeZone":{"$id":"5","$type":"System.TimeZoneInfo, mscorlib","Id":"Eastern Standard Time","DisplayName":"(UTC-05:00) Eastern Time (US & Canada)","StandardName":"Eastern Standard Time","DaylightName":"Eastern Daylight Time","BaseUtcOffset":"-PT5H","AdjustmentRules":[{"$id":"6","$type":"System.TimeZoneInfo+AdjustmentRule, mscorlib","DateStart":"0001-01-01T00:00:00.000","DateEnd":"2006-12-31T00:00:00.000","DaylightDelta":"PT1H","DaylightTransitionStart":{"$id":"7","$type":"System.TimeZoneInfo+TransitionTime, mscorlib","TimeOfDay":"0001-01-01T02:00:00.000","Month":4,"Week":1,"Day":1,"DayOfWeek":"Sunday","IsFixedDateRule":false},"DaylightTransitionEnd":{"$id":"8","$type":"System.TimeZoneInfo+TransitionTime, mscorlib","TimeOfDay":"0001-01-01T02:00:00.000","Month":10,"Week":5,"Day":1,"DayOfWeek":"Sunday","IsFixedDateRule":false}},{"$id":"9","$type":"System.TimeZoneInfo+AdjustmentRule, mscorlib","DateStart":"2007-01-01T00:00:00.000","DateEnd":"9999-12-31T00:00:00.000","DaylightDelta":"PT1H","DaylightTransitionStart":{"$id":"10","$type":"System.TimeZoneInfo+TransitionTime, mscorlib","TimeOfDay":"0001-01-01T02:00:00.000","Month":3,"Week":2,"Day":1,"DayOfWeek":"Sunday","IsFixedDateRule":false},"DaylightTransitionEnd":{"$id":"11","$type":"System.TimeZoneInfo+TransitionTime, mscorlib","TimeOfDay":"0001-01-01T02:00:00.000","Month":11,"Week":1,"Day":1,"DayOfWeek":"Sunday","IsFixedDateRule":false}}],"SupportsDaylightSavingTime":true},"Users":[{"$ref":"1"}],"Groups":[],"IsArchived":false,"ApartmentComplexId":1,"Id":1},"Id":2},"ResidentId":3,"Resident":{"$ref":"1"},"LeaseStatusId":4,"LeaseStatus":{"$id":"12","$type":"RadiusBlue.Core.Models.LeaseStatus, RadiusBlue.Core","Description":"Lost","Id":4},"Id":1},{"$id":"13","$type":"RadiusBlue.Core.Models.Lease, RadiusBlue.Core","Start":"2013-05-24T00:00:00.000","End":"2014-05-24T00:00:00.000","UnitId":1,"Unit":{"$id":"14","$type":"RadiusBlue.Core.Models.Unit, RadiusBlue.Core","Building":"A","Floor":2,"ModelName":"Aqua","RentAmount":2000.00,"NumberOfBeds":2,"NumberOfBaths":1,"UnitName":"101A","IsInactive":true,"Inhabitants":[{"$ref":"1"}],"ApartmentComplexId":1,"ApartmentComplex":{"$ref":"4"},"Id":1},"ResidentId":3,"Resident":{"$ref":"1"},"LeaseStatusId":1,"LeaseStatus":{"$id":"15","$type":"RadiusBlue.Core.Models.LeaseStatus, RadiusBlue.Core","Description":"Active","Id":1},"Id":2}],"CurrentUnitId":1,"CurrentUnit":{"$ref":"14"},"ApartmentComplexId":1,"ApartmentComplex":{"$ref":"4"},"Id":3,"User":{"$id":"16","$type":"RadiusBlue.Core.Models.UserProfile, RadiusBlue.Core","UserName":"vjiawon#gmail.com","FirstName":"Vishal","LastName":"Jiawon","Age":27,"PhoneNumber":"123 456 7890","IsInactive":false,"UserDetail":{"$ref":"1"},"GroupMembers":[],"MaintenanceRequests":[],"Id":3},"IsArchived":false,"LastUpdatedById":1,"LastUpdatedDate":"0001-01-01T00:00:00.000+00:00","IsInactive":false,"CreatedById":1,"CreatedDate":"0001-01-01T00:00:00.000+00:00"}]
I do not doubt that there is a bug in BreezeJS somewhere.
I can report that, at least as of v.1.3.4, Breeze can expand multiple navigation properties of a TPH class ... and not just on the first entity returned.
I just modified the "can navigate to AccountType eagerly loaded with expand" test in inheritanceTests.js in DocCode so that (a) it also expands the Status navigation and (b) the tests are performed on the 3rd entity returned rather than the 1st.
The query is something like this:
var em = newEm(); // clean, empty EntityManager
return EntityQuery.from('bankRootTPHs').take(3)
.expand('AccountType, Status'))
.using(em).execute().then(success).fail(handleFail);
...
function success(data) {
var entity = data.results[data.results.length-1]; // get the last one (the 3rd)
var type = data.query.entityType.shortName;
if (!entity) {
ok(false, "a query failed to return a single " + type);
}
// more tests
// I just set a breakpoint and inspected
// entity.accountType() and entity.status()
// Both returned the expected related entities
}
I see that both the related AccountType and the related Status are available from the entity.
So something else is wrong.
Questions about your Example
First I am compelled to observe that you have a lot of expands. I count 5 related entities. That can hurt performance. I know we're not talking about that but I'm calling it out.
Second, the super class UserDetail is concrete but the intermediate derived class UserBelongsToApartmentComplex is abstract. You have inheritance class hierarchies that go concrete/abstract/concrete. The queried type, Residents is one such class. And a class at every level maps to the "UserDetail" table, yes?
I'm pretty sure we didn't test for that scenario ... which is pretty uncommon. I wasn't even sure that worked! For now I have to take your word for it that EF allows such a construct.
It would seem that BreezeJS is confused about it. We'll take a look.

How do I create a navigation property that can navigate to more than one entity type?

I have the following in my domain classes ( simplified )
public enum JobType
{
SalesOrder = 1,
StockOrder = 2
}
public class SalesOrder : LoggedEntity
{
public string Name { get; set; } // and other fields
}
public class StockOrder : LoggedEntity
{
public string Name { get; set; } // and other fields
}
public class Job : LoggedEntity
{
public int JobType { get; set; } // jobtype enum
public virtual LoggedEntity LinkedEntity { get; set; }
}
My context is as follows;
public class Context : DbContext
{
public DbSet<Job> Jobs { get; set; }
public DbSet<StockOrder> StockOrders { get; set; }
public DbSet<SalesOrder> SalesOrders { get; set; }
}
When I run the migration i get the error described [here][1] So using an abstract entity appears not to work.
My question was, how do I create a navigation property that can navigate to more than one entity type?
If JobType = SalesOrder then I want to navigate to sales order, if JobType = StockOrder then I want to navigate to stock order.
I wanted to use a Table Per Heirarchy Strategy [see TPH here][2]
The trick is to keep EF oblivious of the LoggedEntity class. Remodel your entities according to this example:
public enum JobType
{
SalesOrder = 1,
StockOrder = 2
}
public abstract class LoggedEntity
{
public int Id { get; set; }
public string Name { get; set; } // and other fields
}
public abstract class BaseOrder : LoggedEntity // New base class for orders!!
{ }
public class SalesOrder : BaseOrder
{ }
public class StockOrder : BaseOrder
{ }
public class Job : LoggedEntity
{
public JobType JobType { get; set; } // jobtype enum
public virtual BaseOrder Order { get; set; }
}
public class Tph2Context : DbContext
{
public DbSet<Job> Jobs { get; set; }
public DbSet<BaseOrder> Orders { get; set; }
}
You will see that the migration creates two tables, Jobs and BaseOrders (name to be improved). Job now has a property Order that can either be a SalesOrder or a StockOrder.
You can query specific Order types by
contex.Orders.OfType<StockOrder>()
And you will notice that EF doesn't know LoggedEntity, because
context.Set<LoggedEntity>()
will throw an exception
The entity type LoggedEntity is not part of the model for the current context.
how do I create a navigation property that can navigate
to more than one entity type?
You cannot do so. atleast not now. navigational properties are way of describing relationship between entities. at most, they represent, some sort of sql relationship. so you cannot alter or define such a relationship on the fly. you have to define it before hand.
Now in order to do that, you have to define separate navigational property for your separate conditions i.e.
public class Job : LoggedEntity
{
public int JobTypeSales { get; set; }
public int JobTypeStock { get; set; }
public virtual SalesOrder SalesOrder { get; set; }
public virtual StockOrder StockOrder { get; set; }
}
and then link them in configuration in modelbuilder through fluent API.
HasRequired(s => s.SalesOrder)
.WithMany()
.HasForeignKey(s => s.JobTypeSales).WillCascadeOnDelete(true);
HasRequired(s => s.StockOrder)
.WithMany()
.HasForeignKey(s => s.JobTypeStock).WillCascadeOnDelete(true);
and
as for your error "Sequence Contains No Elements"
this error comes, when the Linq query that you specified, is using either .First() or .Single(), or .ToList() and query returned no data.
so to avoid it use, .FirstOrDefault() or SingleOrDefault().
obviously with proper null check.

ServiceStack not receiving values on OnDelete

On OnDelete of ServiceStack, it is called but the values are empty.
I tried to check the value, e.g.
ProductRequestResponse rx = Client.Send<ProductRequestResponse>(
"DELETE", "http://localhost:2012/api/product_request",
new ProductRequest { Id = 7 });
On the ServiceStack side, I only receive an Id of 0. Here's my StackService OnDelete method.
public override object OnDelete(ProductRequest request)
{
throw new Exception("Id: " + request.Id.ToString());
}
Here's my objects use for communication
public class ProductRequest
{
public int Id { get; set; }
public ProductDto ProductDto { get; set; }
}
public class ProductRequestResponse
{
public ProductDto ProductDto { get; set; }
public IEnumerable<ProductDto> ProductDtos { get; set; }
public ServiceStack.ServiceInterface.ServiceModel.ResponseStatus ResponseStatus { get; set; } //Where Exceptions get auto-serialized
}
What am I missing, why StackService is not receiving any value from OnDelete method?
Firstly, you should be using the Delete method as the Send only does POST's:
So it looks something like:
restClient.Delete<TransactionLogResponse>("/transactionlog");
The reason why Delete doesn't expect a Request DTO is because the DELETE Http verb does not accept a request body.
If you want to add paramaters you should add this on the route path or query string, e.g:
restClient.Delete<TransactionLogResponse>("/transactionlog/1?Arg1=a&Arg2=b");

Resources