I have a simple case statement within my query that essentially says if the count of the subquery is 1 return false, else return true. The query generated by NHibernate is correct however I'm having a hard time getting the boolean return value of the case statement to transform into my dto.
Here is my restriction
var restriction = Restrictions.Eq(Projections.SubQuery(QueryOver.Of(() => alias)
.Select(Projections.Count(() => alias.Id))
.Where(x => x.AddedBy == UserProvider.Current.UserName)
.Where(() => alias.Item.Id == alias2.Id)), 1);
And here is my projection
.Add(
Projections.Conditional(restriction,
Projections.Cast(NHibernateUtil.YesNo, Projections.Constant(false)),
Projections.Cast(NHibernateUtil.YesNo, Projections.Constant(true))))
.WithAlias(() => dto.CanApply))
)
Here, the CanApply property from my dto is a boolean. The error I receive is {"The type System.Int32 can not be assigned to a property of type System.Boolean setter of Dto.CanApply"}.
I've tried everything I can think of to try and make NHibernate transform an integer to boolean.
What am I missing?
Edit
I was able to resolve this by creating a custom result transformer by inheriting IResultTransformer and checking each aliases to see if it's the one causing the issue. Once I found the culprit alias I simply issue a Convert.ToBoolean(tupleValue) and call it a day.
Can anyone please confirm whether I'm re-inventing the wheel or does NHibernate have a built in mechanism that I'm missing for doing such thing?
Try specifying the type in Projections.Constant. Like this:
.Add(
Projections.Conditional(restriction,
Projections.Constant(false, NHibernateUtil.Boolean)),
Projections.Constant(true, NHibernateUtil.Boolean))))
.WithAlias(() => dto.CanApply))
)
Related
I have an OData Controller which looks pretty standard.
[HttpGet]
[ODataRoute("GridData")]
[EnableQuery]
public async Task<IQueryable<GridData>> GetGridData(ODataQueryOptions<GridData> odataOptions)
{
var query = odataOptions.ApplyTo(_service.GetGridDataQueryable()) as IQueryable<GridData>
return query;
}
Projection looks like this :
.Select(async x =>
{
//Pretty resource heavy
x.Ownership = await _ownershipService.ComputeAsync(_currentUser));
return x;
})
.Select(t => t.Result)
.ToList();
Now the problem is that I need to actually return a GridDataDTO object from this call. There is some processing that cannot be done at the database level. The processing is pretty heavy so I would not like to add it inside the GetGridDataQueryable().Also the processing is async, and need a materialized result set to be able to apply it.
I also need to return the IQueryable in the controller to be able to benefit from $count, $select, etc .
This hooks up to a pretty complex grid with a lot of options for filtering/sorting so I would not like to remove the OData functionality.
Is there a simple way to add postprocessing here ? After the result is materialized, project it to my GridDataDTO ?
There is no need for insert/update/delete support, as this will be only used for read operations.
There is no requirement for your controller method to only pass through a query from the database, in fact your method does not need to return an IQueryable<T> result at all!
You can still benefit from OData $select, $expand and $filter operators on result sets that are not IQueryable<T>, but you lose most of the performance benefits of doing so and you have to prepare you data so that the operators can be processed, and you will have to explicitly decorate your endpoint with the [EnableQuery] attribute.
In the following example you current query is materialized into memory, after applying the query options, then we can iterate over the set and manipulate it as we need to.
In the end the same recordset, with the modified records is returned, cast as queryable to match the method signature, however the method would still function the same if the result was IEnumerable<T>
There is a strong argument that says you should return IEnumerable<T> because it conveys the correct information that the recordset has been materialized and is not deferred.
[HttpGet]
[ODataRoute("GridData")]
public async Task<IQueryable<GridDataDTO>> GetGridData(ODataQueryOptions<GridData> odataOptions)
{
// NOTE: GridDataDTO : GridData
// apply $filter, $top and $skip to the DB query
IQueryable<GridData> query = odataOptions.ApplyTo(_service.GetGridDataQueryable());
// materialize
var list = query.ToList();
// project into DTO
List<GridDataDTO> output = list.Select(async x =>
{
var o = new GridDataDTO(x);
o.Ownership = await _ownershipService.ComputeAsync(_currentUser));
}).ToList();
// return, as Queryable
return output.AsQueryable();
}
UPDATE:
When the manipulations involve projection into a new type, then to properly support OData query options the type defined in your ODataQueryOptions<> needs to be assignable from the output element type. You can do this through inheritance or with implicit cast definitions.
If an explicit cast is required (or no cast is available at all) then you will have to manually validate the ApplyTo logic, the ODataQueryOptions must be a valid type reference to match the output.
I'm using AutoMapper .ProjectTo<OrderDto>() to map some Order to OrderDto.
The original Order is a pretty big object with around 30 properties and 15 collections.
For my OrderDto I have a property DisplayStatus with some custom logic based on 4 properties in the original Order. I don't need those properties in my final OrderDto, I just need them to do something like GetDisplayStatus().
I have tried several ways of doing this and none of them feels satisfying to me so my guess is I must be doing something wrong. There has to be a better way.
FIrst thing I tried was to use a custom value resolver but you can't use them inside an IQueryable projection because they can't be translated to Linq (if I understood correctly).
Then I tried doing something like this
.ForMember(target => target.DisplayStatus, option => option.MapFrom(source => GetDisplayStatus(source))
The problem with this is that Entity Framework consider this to be a "blackbox" and have no way to know what I need and what I don't. Therefore the whole source object is passed to the function. The consequence is that the final SQL Query applies a Select on all the columns from Order, bloating the query with a big chunk of unnecessary data and performance goes down by a big margin.
In the end the current way of doing this in our codebase is something like this
.ForMember(target => target.DisplayStatus, option => option.MapFrom(source =>
GetDisplayStatus(new TempOrder{
someProperty = source.someProperty
anotherPropery = source.anotherProperty
}))
By doing this I'm able to narrow down exactly what properties of the original Order I want to pass to my GetDisplayStatus() function and the final SQL Query stay clean.
Downside to this is that the code can quickly become pretty ugly when the things you want to pass to your function require a bit a work themselves.
.ForMember(target => target.DisplayStatus, option => option.MapFrom(source =>
GetDisplayStatus(source.OrderProducts.Where(//a lot of work here too//).Select(op =>
new TempOrderProduct
{
ProductTypeId = op.ProductTypeId,
HasFiles = op.OrderFiles.Any(),
VisitDate = op.VisitDate
}), source.OrderStatus)))
Again, this does not feel right. Is there a better way to do this ?
Thank you.
Edit
Here is the same query without AutoMapper as requested in the comments
public static IQueryable<OrderDTO> MapOrderDTO(this IQueryable<Orders> orders)
{
return orders
.Select(order => new OrderDTO
{
OrderID = order.OrderId,
OrderCreationDate = order.OrderCreationDate,
OrderStatus = order.OrderStatus,
DisplayStatus = GetDisplayStatus(order.OrderProducts
.Where(//a lot of work//)
.Select(op => new TempOrderProduct
{ ProductTypeId = op.ProductTypeId,
HasFiles = op.OrderFiles.Any(),
VisitDate = op.VisitDate }),
order.OrderStatus)
})
});
}
I have a simple GET method, which returns IQueryable, and has some preconditions on query:
[Queryable(HandleNullPropagation = HandleNullPropagationOption.False)]
public IQueryable<Message> Get()
{
using (var session = RavenStore.GetSession())
{
var messages = session.Query<Message>().Where(x => x.TargetUserId == this.User.Identity.Name || x.SourceUserId == this.User.Identity.Name);
return messages;
}
}
This is RavenDB, btw. The issue I'm having is that upon execution the user id is replaced with "[EMPTY_STRING]", so the actual query its running is this:
'TargetUserId:[[EMPTY_STRING]] OR SourceUserId:[[EMPTY_STRING]]' on
index .....
which is obviously wrong.
If I'm returning List instead of IQueriable - it works fine, so something later in the pipeline changes the query. Does anyone have any insight on how to make this work ?
It should work when the values are copied to a local variable first:
var userName = this.User.Identity.Name;
return session.Query<Message>()
.Where(x => x.TargetUserId == userName ||
x.SourceUserId == userName);
This is because by the time the query is executed, the Raven Client query translator can't resolve the objects expressed in the predicate. By copying them into a local variable, you are passing a constant value into the expression.
I believe this is related to closures. Perhaps someone with more direct knowledge of expression trees can explain better in comments.
I'm using Machine.Fakes.NSubstitute and want to "fake" a return value such that if the input parameter matches a specific value it returns the mock object, otherwise it returns null.
I tried the following:
host.WhenToldTo(h => h.GetTenantInstance(Param.Is(new Uri("http://foo.bar"))))
.Return(new TenantInstance());
But it throws the following exception:
System.InvalidCastException: Unable to cast object of type
'System.Linq.Expressions.NewExpression' to type
'System.Linq.Expressions.ConstantExpression'.
My current workaround is to do the following:
host.WhenToldTo(h => h.GetTenantInstance(Param.IsAny<Uri>()))
.Return<Uri>(uri => uri.Host == "foo.bar" ? new TenantInstance() : null);
Which is a bit smelly.
I see three aspects here:
When a method with a reference type return value is called on a mock object and no behavior has been set up for the call, the mock object will return a mock. If you want it to return null instead, you have to configure that explicitly. Thus, it is not enough to set up
host.WhenToldTo(h => h.GetTenantInstance(Param.Is(new Uri("http://foo.bar"))))
.Return(new TenantInstance());
You also have to set up the other case with something like this:
host.WhenToldTo(h => h.GetTenantInstance(Param<Uri>.Matches(x => !x.Equals(new Uri("http://foo.bar")))))
.Return((TenantInstance)null);
I find your "workaround" solution more elegant than these two setups.
When you match a method call argument for equality, there is no need to use Param.Is(). You can simply set up the behavior with
host.WhenToldTo(h => h.GetTenantInstance(new Uri("http://foo.bar")))
.Return(new TenantInstance());
The fact that you get an exception when using Param.Is() here is a shortcoming of Machine.Fakes. I see not reason why this should not work. I will correct that at some point and let you know.
Here's my (simplified) model: Ticket -> Customer Callback (s)
I have my Ticket mapped so that when it's loaded, the Callbacks are as well.
base.HasMany<TechSupportCallback>(x => x.Callbacks)
.KeyColumn(Fields.TRACKED_ITEM_ID)
.Not.LazyLoad()
.Inverse()
.Cache.ReadWrite();
This is not lazy loading because otherwise I'll get 'no session to load entities' when the web service tries to serialize (and load) the proxy. (Using repositories to fetch data.)
It's also bi-directional .. (in the CallbackMap)
base.References(x => x.Ticket)
.Column(Fields.TRACKED_ITEM_ID)
.Not.Nullable();
Now .. we need to show an agent a list of their callbacks - JUST their callbacks.
-- When I query using Criteria for the Callbacks, I cannot prevent the Ticket (and subsequently it's entire graph, including other collections) from being loaded. I had previously tried to set FetchMode.Lazy, then iterate each resulting Callback and set Ticket to null, but that seems to be ignored.
// open session & transaction in using (..)
var query = session.CreateCriteria<TechSupportCallback>()
.SetCacheable(true)
.SetCacheRegion("CallbacksByUserAndGroups")
.SetFetchMode("Ticket", FetchMode.Lazy) // <-- but this doesn't work!
.SetMaxResults(AegisDataContext.Current.GetMaxRecordCount())
;
rValue = query.List<TechSupportCallback>();
rvalue.ForEach(x => x.Ticket = null;); // <-- since this is already retrieved, doing this merely prevents it from going back across the wire
tx.Commit();
// usings end (..)
Should I be doing this with a projection instead?
The problem with that .. is I've not been able to find an example of projections being used to populate an entity, or a list of them -- only to be used as a subquery on a child entity or something similar to restrict a list of parent entities.
I could really use some guidance on this.
[EDIT]
I tried using a projection as suggested but:
I'm getting the following: (this was because of a bug, and so I've since stopped using the cache and it works. http://nhibernate.jira.com/browse/NH-1090)
System.InvalidCastException occurred
Message=Unable to cast object of type 'AEGISweb.Data.Entities.TechSupportCallback' to type 'System.Object[]'.
Source=NHibernate
StackTrace:
at NHibernate.Cache.StandardQueryCache.Put(QueryKey key, ICacheAssembler[] returnTypes, IList result, Boolean isNaturalKeyLookup, ISessionImplementor session)
InnerException:
at
rValue = query.List<TechSupportCallback>();
with the projection list defined like
// only return the properties we want!
.SetProjection(Projections.ProjectionList()
.Add(Projections.Alias(Projections.Id(), ex.NameOf(x => x.ID))) //
.Add(Projections.Alias(Projections.Property(ex.NameOf(x => x.ContactID)), ex.NameOf(x => x.ContactID)))
// ...
)
.SetResultTra...;
rValue = query.List<TechSupportCallback>();
mapped like
public TechSupportCallbackMap()
{
base.Cache.ReadWrite();
base.Not.LazyLoad();
base.Table("TS_CALLBACKS");
base.Id(x => x.ID, Fields.ID)
.GeneratedBy.Sequence("SEQ_TS_CALLBACKS");
base.References(x => x.Ticket)
.Column(Fields.TRACKED_ITEM_ID)
.Not.Nullable();
base.Map(x => x.TrackedItemID, Fields.TRACKED_ITEM_ID)
.Not.Insert()
.Not.Update()
.Generated.Always()
;
// ...
}
This sounds like it's a job exactly for projections.
var query = session.CreateCriteria<TechSupportCallback>()
.SetCacheable(true)
.SetCacheRegion("CallbacksByUserAndGroups")
.SetFetchMode("Ticket", FetchMode.Lazy)
.SetMaxResults(AegisDataContext.Current.GetMaxRecordCount())
.SetProjection(Projections.ProjectionList().
.Add(Projections.Alias(Projections.Id(), "Id")
.Add(Projections.Alias(Projections.Property("Prop"), "Prop")))
.SetResultTransformer(Transformers.AliasToBean<TechSupportCallback>())
;
Simply list all the properties you want to include and exclude the Ticket entity. You can even create a POCO class simply for encapsulating the results of this query. Rather than using an existing entity class.