RavenDb LuceneQuery: How to query on List<string> - lucene

How can I make a query on List<string> with Lucene? Here is my attempt, but I got an error.
var top10MoviesQuery = session.Advanced.DocumentQuery<Media, Media_Index>()
.SetResultTransformer("MediaListTransformer")
.SelectFields<MediaListProjection>()
.WhereEquals(o => o.AvalibleOnServices, serviceMovie) <---- here
.OrderByDescending(o => o.OurScore)
.Take(10)
.Lazily();
AvalibleOnServices is a List<string>() and serviceMovie is a string.

I'm assuming your purpose is to query documents which AvailableOnServices list contains serviceMovie. If that's the case you could just do something like this, you don't need Lucene for such a simple thing:
var top10MoviesQuery = session.Query<Media, Media_Index>()
.Where(x => x.AvailableOnServices.Contains(serviceMovie))
.Take(10)
.TransformWith<MediaListTransformer, MediaListProjection>()
.Lazily();
Note that:
Here I assume that MediaListProjection is what MediaListTransformer transforms into, since you didn't mention otherwise, and
you need to add using Raven.Client.Linq; to be able to invoke TransformWith on the results - by importing this namespace you will get an IRavenQueryable<T> instead of a regular IQueryable<T> when you call Where, so you will be able to invoke Raven-specific stuff such as TransformWith and Lazily.

Related

How to add post processing to .Net Core OData?

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.

Optimal way to use function for property in AutoMapper ProjectTo IQueryable tree

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)
})
});
}

Compilation error with QueryOver and multiple SubQueries

I'm getting the following compilation error when I'm using QueryOver with a list of sub queries:
"The type arguments for method 'xxxx' cannot be inferred from the usage. Try specifying the type arguments explicitly."
Here is the code but not sure how I can correct it:
List<QueryOver> subQueries = new List<QueryOver>();
subQueries.Add(QueryOver.Of<Customer>().Where(...));
subQueries.Add(QueryOver.Of<Address>().Where(...));
subQueries.Add(QueryOver.Of<Account>().Where(...));
var query = session.QueryOver<Customer>();
foreach (QueryOver subQuery in subQueries)
{
query.WithSubquery.WhereProperty(c => c.CustomerID)
.In(subQuery); // this is throwing the compilation error
}
var result = query.Select(Projections.RowCount())
.FutureValue<int>()
.Value;
I need to do this programatically as I am generating the subQueries dynamically and don't know how many sub queries there will be. Do I need to use dynamic types or something?
I think you can get around this by rewriting things slightly:
foreach (QueryOver subQuery in subQueries)
{
query.Where(
Restrictions.EqProperty(
Projections.Property<Customer>(c => c.CustomerID),
Projections.SubQuery(subQuery.DetachedCriteria)));
}
I don't know enough about your subqueries to say, but you might be able to use a List<QueryOver<Customer>> instead. The issue is that .WithSubquery...In requires a QueryOver<U> where your list is the non-generic base type (QueryOver).
I've managed to solve this with a little refactoring. Andrew's response gave me the idea to use .Where() (instead of .WithSubquery) and then use a Conjunction for the sub queries. The refactored code looks like this:
Conjunction conj = new Conjunction();
conj.Add(Subqueries.WhereProperty<Customer>(x => x.CustomerID).In(QueryOver.Of<Customer>()...));
conj.Add(Subqueries.WhereProperty<Customer>(x => x.CustomerID).In(QueryOver.Of<AttributeValue>()...));
conj.Add(Subqueries.WhereProperty<Customer>(x => x.CustomerID).In(QueryOver.Of<CustomerListEntry>()...));
ISession session = sf.OpenSession();
using (var tran = session.BeginTransaction())
{
var query = session.QueryOver<Customer>()
.Where(conj)
.Select(Projections.RowCount())
.FutureValue<int>()
.Value;
tran.Commit();
}
I can now programatically build up and selectively apply the subqueries.

Full text search using linq

I have a list of names in a observable collection returned from wcf service and database is oracle, I want to make full text search on the list using LINQ.
service is consumed in silverlight application.
Any suggestions pls.
How about this?
var found = thelist.Where(str => str.Contains(strToSearchFor));
or maybe this -
var found = thelist.Where(str => str.ToLower().Contains(strToSearchFor.ToLower()));
if it is not list of strings it would like like this:
var found = thelist.Where(obj => obj.strProperty.Contains(strToSearchFor));
If you need this solution to be case insensitive the solution of Hogan can be done without creatring a new string (by using the ToLower() method).
First, create an extension method:
public static class Extensions
{
public static bool Contains(this string source, string stringToMatch, StringComparison comparisonType)
{
return source.IndexOf(stringToMatch, comparisonType) >= 0;
}
}
Then you can make the Hogan solution case insensitive like this:
var found = thelist.Where(str => str.Contains(strToSearchFor, StringComparison.OrdinalIgnoreCase));

How do you work with detached QueryOver instances?

This NHibernate blog entry notes how detached QueryOver queries (analogous to DetachedCriteria) can be created (using QueryOver.Of<T>()). However, looking this over, it doesn't look analogous to me at all.
With DetachedCriteria, I would create my instance and set it up however I need, and afterwards call GetExecutableCriteria() to then assign the session and execute the query. With the "detached" QueryOver, most of the API is unavailable (ie, to add restrictions, joins, ordering, etc...) until I call GetExecutableQueryOver, which requires takes an ISession or IStatelessSession, at which point you are no longer disconnected.
How do you work with detached QueryOver instances?
EDIT:
Actual problem was related to how I'm storing the detached QueryOver instance:
public class CriteriaQuery<T>
{
internal protected QueryOver<T> _QueryOver { get; set; }
public CriteriaQuery()
{
_QueryOver = QueryOver.Of<T>();
}
// Snip
}
It should be a QueryOver<T, T>.
I'm using NHibernate 3.1.0.4000. The following code compiles successfully:
Employee salesRepAlias = null;
var query = QueryOver.Of<Customer>()
.JoinAlias(x => x.SalesRep, () => salesRepAlias)
.Where(x => x.LastName == "Smith")
.Where(() => salesRepAlias.Office.Id == 23)
.OrderBy(x => x.LastName).Asc
.ThenBy(x => x.FirstName).Asc;
return query.GetExecutableQueryOver(session)
.List();
This illustrates using restrictions, joins, and ordering on a detached QueryOver just like you would with a regular one.
Could you please post the code that demonstrates the API features that are unavailable?