I'm trying to create a static index where I want all documents where a key exists and has a value. The value itself is not important, only the key exists.
I'm exploring this example with dynamic fields:
https://ravendb.net/docs/article-page/2.5/csharp/client-api/advanced/dynamic-fields
... and although I'm getting the index to work, I'm not sure if the query I'm using is correct.
This is the sample class:
public class Result
{
public Dictionary<string, List<Data>> Results { get; set; }
}
The key in the dictionary is the ID of a user (for example "user/1") and the value is a list of data-objects. The so the json-structure looks like this:
{
"Results" :
{
"user/1": [{...}],
"user/2": [{...}],
}
}
The index I use is this:
public class Result_ByUserId : AbstractIndexCreationTask<Result>
{
public Result_ByUserId()
{
Map = res => from r in res
select new
{
_ = r.Results
.Select(d => CreateField(d.Key, d.Value))
};
}
}
My problem comes down to the query, as it assumes I want to look at a specific key and value.
var resultat = session.Advanced.DocumentQuery<Result>("Result/ByUserId ")
.WhereEquals("user/1", "") // How do I write a !isNullOrEmpty?
.ToList();
... which I don't want to do. I only want the results that has a key in which the value is not null or empty. Does anybody have any good tips?
What you can do is index a boolean flag depending on if the dictionary has a value or not and then query on that.
public class Result_ByUserId : AbstractIndexCreationTask<Result>
{
public Result_ByUserId()
{
Map = res => from r in res
select new
{
_ = r.Results
.Select(d => CreateField(d.Key, d.Value != null ? true : false, false, true))
};
}
}
The query can then be:
var resultat = session.Advanced.DocumentQuery<Result>("Result/ByUserId ")
.WhereEquals("user/1", true)
.ToList();
This will return any Result documents that has a Dictionary with a key of user/1 and a dictionary value that's not null.
Not sure it's the best way of doing it, but it worked for me...
Hope this helps!
Related
I want to prevent documents from being deleted in my project and I decided to use metadata to mark document as Archived. I used below code to do that:
public class DeleteDocumentListener : IDocumentDeleteListener
{
public void BeforeDelete(string key, object entityInstance, RavenJObject metadata)
{
metadata.Add("Archived", true);
throw new NotSupportedException();
}
}
After that I wanted to alter query to return only documents which have Archived metadata value set to false:
using (var session = _store.OpenSession())
{
var query = session.Advanced.DocumentQuery<Cutter>()
.WhereEquals("#metadata.Archived", false);
}
Unfortunately this query return empty result set. It occurs that if Document doesn't have this metadata property then above condition is treated as false. It wasn't what I expected.
How can I compose query to return Documents which don't have metadata property or this property has some value ?
You can solve it by creating an index for you Cutter documents and then query against that:
public class ArchivedIndex : AbstractIndexCreationTask<Cutter>
{
public class QueryModel
{
public bool Archived { get; set; }
}
public ArchivedIndex()
{
Map = documents => from doc in documents
select new QueryModel
{
Archived = MetadataFor(doc)["Archived"] != null && MetadataFor(doc).Value<bool>("Archived")
};
}
}
Then query it like this:
using (var session = documentStore.OpenSession())
{
var cutters = session.Query<ArchivedIndex.QueryModel, ArchivedIndex>()
.Where(x => x.Archived == false)
.OfType<Cutter>()
.ToList();
}
Hope this helps!
Quick side note. To create the index, the following code may need to be run:
new ArchivedIndex().Execute(session.Advanced.DocumentStore);
When I add the index below to my raven database a simple query like
return Session.Query<R>().FirstOrDefault(x => x.RId == Id);
Always returns null. Only after forcing Raven to remove my custom index does desired functionality return. Why is this?
The Index with side effects:
public class RByLatestCommentIndex : AbstractIndexCreationTask<R>
{
public RByLatestCommentIndex()
{
SetMap();
}
void SetMap()
{
Map = r => r.Select(x => new
{
Id = x.Id,
TimeStamp = x.Comments.Count() > 0 ? x.Comments.Max(u => u.Created)
: x.Created
}).OrderByDescending(y => y.TimeStamp).Select(r => new { Id = r.Id });
}
}
public class RIdTransformer : AbstractTransformerCreationTask<R>
{
public RIdTransformer()
{
TransformResults = ids => ids.Select(x => LoadDocument<R>(x.Id));
}
}
EDIT:
In response to Ayende Rahien's comment:
There's a query in the DB which would otherwise be used (Auto/R/ByRID) but the index used looks like this, puzzling enough:
from doc in docs.Rs select new { Images_Count__ = doc.Images["Count()"], RId = doc.RId }
What explains this behaviour? And, will I have to add a static index to be able to query R by RId ?
I have following two Collections in RavenDB.Please help me for creating index for getting data from both collection.
public class Ticket
{
public string TicketID{get;set;}
public double Total{get;set;}
}
public class ImportTiming
{
public string Id{get;set;}
public DateTime ExtractTime{get;set;}
}
AND
public class ResultClass
{
public string TicketID{get;set;}
public double Total{get;set;}
public DateTime ExtractTime{get;set;}
}
TicketID(Ticket) & Id(ImportTiming) are same.I am using LoadDocument for ExtractTime but it is showing NULL value.
Thanks in advance!!!
Finally i got solution...
Bellow is the Map-Reduce function,in which i have used LoadDocument<> for selecting data from ImportTiming Document.
public class IdxJoinBetweenCollections : AbstractIndexCreationTask<Ticket,JoinBetweenCollections.ResultClass>
{
public IdxJoinBetweenCollections()
{
Map = docs => from doc in docs
let TimeDoc = LoadDocument<ImportTiming>("ImportTiming/" + doc.TicketID)
select new
{
ID = doc.TicketID,
Total = doc.Total,
ExtractTime = TimeDoc.ExtractComplete,
};
Reduce = results => from res in results
group res by res.ID into g
select new
{
ID = g.Key,
Total = g.Select(x => x.Total).FirstOrDefault(),
ExtractTime = g.Select(x => x.ExtractTime).FirstOrDefault(),
};
}
}
In LoadDocument("ImportTiming/" + doc.TicketID),i have used CollectionName followed by Id so that i gets whole document.If i dont use CollectionName then it shows NULL value.
Reference:http://ravendb.net/docs/2.0/client-api/querying/static-indexes/indexing-related-documents
I modified the "Read" operation on my Windows Azure Mobile Services Preview table (named "Item") as follows:
Javascript:
function read(query, user, request)
{
var howRead;
if(howRead == "unique")
{
var sqlUnique = "SELECT DISTINCT ? FROM Item WHERE qProjectCode = ?";
mssql.query(sqlUnique)
request.execute();
}
else if (howRead == "column")
{
var sqlColumn = "SELECT ? FROM Item WHERE qProjectCode = ?";
mssql.query(sqlColumn)
request.execute();
}
else if (howRead == "all")
{
var sqlAll = "SELECT * FROM Item WHERE qProjectCode = ?";
mssql.query(sqlAll)
request.execute();
}
}
This simply species when I want a unique list of a single column's values returned, all items in a single column, or all columns, respectively, all while limiting the read to those records with a given project code.
Right now, this works in C#, but scans the entire table (with other project codes) and always returns all columns. This is inherently inefficient.
c#
var client = new MobileServiceClient("[https path", "[key]");
var table = client.GetTable<Item>();
var query1 = table.Where(w => w.QProjectCode == qgv.projCode && w.QRecord == (int)lbRecord.Items[uStartRecordIndex]);
var query1Enum = await query1.ToEnumerableAsync();
foreach (var i in query1Enum)
{
// process data
}
How do I alter the c# code to deal with the Javascript code? Feel free to critique the overall approach, since I am not a great programmer and can always use advice!
Thanks
A few things:
In your server code, the mssql calls are not doing anything (useful). If you want to get their results, you need to pass a callback (the call is asynchronous) to it.
Most of your scenarios can be accomplished at the client side. The only for which you'll need server code is the one with the DISTINCT modifier.
For that scenario, you'll need to pass a custom parameter to the server script. You can use the WithParameters method in the MobileServiceTableQuery<T> object to define parameters to pass to the service.
Assuming this data class:
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Other { get; set; }
public string ProjectCode { get; set; }
}
The code below can be used to accomplish the scenarios 2 and 3 at the client side only (no script needed at the server side). The other one will need some script, which I'll cover later.
Task<IEnumerable<string>> ReadingByColumn(IMobileServiceTable<Item> table, string projectCode)
{
return table
.Where(i => i.ProjectCode == projectCode)
.Select(i => i.Name)
.ToEnumerableAsync();
}
Task<IEnumerable<Item>> ReadingAll(IMobileServiceTable<Item> table, string projectCode)
{
return table.Where(i => i.ProjectCode == projectCode).ToEnumerableAsync();
}
Task<IEnumerable<string>> ReadingByColumnUnique(IMobileServiceTable<Item> table, string projectCode)
{
var dict = new Dictionary<string, string>
{
{ "howRead", "unique" },
{ "projectCode", projectCode },
{ "column", "Name" },
};
return table
.Select(i => i.Name)
.WithParameters(dict)
.ToEnumerableAsync();
}
Now, to support the last method (which takes the parameters, we'll need to do this on the server script:
function read(query, user, request)
{
var howRead = request.parameters.howRead;
if (howRead) {
if (howRead === 'unique') {
var column = request.parameters.column; // WARNING: CHECK FOR SQL INJECTION HERE!!! DO NOT USE THIS IN PRODUCTION!!!
var sqlUnique = 'SELECT DISTINCT ' + column + ' FROM Item WHERE ProjectCode = ?';
mssql.query(sqlUnique, [request.parameters.projectCode], {
success: function(distinctColumns) {
var results = distinctColumns.map(function(item) {
var result = [];
result[column] = item; // mapping to the object shape
return result;
});
request.respond(statusCodes.OK, results);
}
});
} else {
request.respond(statusCodes.BAD_REQUEST, {error: 'Script does not support option ' + howRead});
}
} else {
// no server-side action needed
request.execute();
}
}
given a query in the form of an ICriteria object, I would like to use NHibernate (by means of a projection?) to find an element's order,
in a manner equivalent to using
SELECT ROW_NUMBER() OVER (...)
to find a specific item's index in the query.
(I need this for a "jump to page" functionality in paging)
any suggestions?
NOTE: I don't want to go to a page given it's number yet - I know how to do that - I want to get the item's INDEX so I can divide it by page size and get the page index.
After looking at the sources for NHibernate, I'm fairly sure that there exists no such functionality.
I wouldn't mind, however, for someone to prove me wrong.
In my specific setting, I did solve this problem by writing a method that takes a couple of lambdas (representing the key column, and an optional column to filter by - all properties of a specific domain entity). This method then builds the sql and calls session.CreateSQLQuery(...).UniqueResult(); I'm not claiming that this is a general purpose solution.
To avoid the use of magic strings, I borrowed a copy of PropertyHelper<T> from this answer.
Here's the code:
public abstract class RepositoryBase<T> where T : DomainEntityBase
{
public long GetIndexOf<TUnique, TWhere>(T entity, Expression<Func<T, TUnique>> uniqueSelector, Expression<Func<T, TWhere>> whereSelector, TWhere whereValue) where TWhere : DomainEntityBase
{
if (entity == null || entity.Id == Guid.Empty)
{
return -1;
}
var entityType = typeof(T).Name;
var keyField = PropertyHelper<T>.GetProperty(uniqueSelector).Name;
var keyValue = uniqueSelector.Compile()(entity);
var innerWhere = string.Empty;
if (whereSelector != null)
{
// Builds a column name that adheres to our naming conventions!
var filterField = PropertyHelper<T>.GetProperty(whereSelector).Name + "Id";
if (whereValue == null)
{
innerWhere = string.Format(" where [{0}] is null", filterField);
}
else
{
innerWhere = string.Format(" where [{0}] = :filterValue", filterField);
}
}
var innerQuery = string.Format("(select [{0}], row_number() over (order by {0}) as RowNum from [{1}]{2}) X", keyField, entityType, innerWhere);
var outerQuery = string.Format("select RowNum from {0} where {1} = :keyValue", innerQuery, keyField);
var query = _session
.CreateSQLQuery(outerQuery)
.SetParameter("keyValue", keyValue);
if (whereValue != null)
{
query = query.SetParameter("filterValue", whereValue.Id);
}
var sqlRowNumber = query.UniqueResult<long>();
// The row_number() function is one-based. Our index should be zero-based.
sqlRowNumber -= 1;
return sqlRowNumber;
}
public long GetIndexOf<TUnique>(T entity, Expression<Func<T, TUnique>> uniqueSelector)
{
return GetIndexOf(entity, uniqueSelector, null, (DomainEntityBase)null);
}
public long GetIndexOf<TUnique, TWhere>(T entity, Expression<Func<T, TUnique>> uniqueSelector, Expression<Func<T, TWhere>> whereSelector) where TWhere : DomainEntityBase
{
return GetIndexOf(entity, uniqueSelector, whereSelector, whereSelector.Compile()(entity));
}
}
public abstract class DomainEntityBase
{
public virtual Guid Id { get; protected set; }
}
And you use it like so:
...
public class Book : DomainEntityBase
{
public virtual string Title { get; set; }
public virtual Category Category { get; set; }
...
}
public class Category : DomainEntityBase { ... }
public class BookRepository : RepositoryBase<Book> { ... }
...
var repository = new BookRepository();
var book = ... a persisted book ...
// Get the index of the book, sorted by title.
var index = repository.GetIndexOf(book, b => b.Title);
// Get the index of the book, sorted by title and filtered by that book's category.
var indexInCategory = repository.GetIndexOf(book, b => b.Title, b => b.Category);
As I said, this works for me. I'll definitely tweak it as I move forward. YMMV.
Now, if the OP has solved this himself, then I would love to see his solution! :-)
ICriteria has this 2 functions:
SetFirstResult()
and
SetMaxResults()
which transform your SQL statement into using ROW_NUMBER (in sql server) or limit in MySql.
So if you want 25 records on the third page you could use:
.SetFirstResult(2*25)
.SetMaxResults(25)
After trying to find an NHibernate based solution for this myself, I ultimately just added a column to the view I happened to be using:
CREATE VIEW vw_paged AS
SELECT ROW_NUMBER() OVER (ORDER BY Id) AS [Row], p.column1, p.column2
FROM paged_table p
This doesn't really help if you need complex sorting options, but it does work for simple cases.
A Criteria query, of course, would look something like this:
public static IList<Paged> GetRange(string search, int rows)
{
var match = DbSession.Current.CreateCriteria<Job>()
.Add(Restrictions.Like("Id", search + '%'))
.AddOrder(Order.Asc("Id"))
.SetMaxResults(1)
.UniqueResult<Paged>();
if (match == null)
return new List<Paged>();
if (rows == 1)
return new List<Paged> {match};
return DbSession.Current.CreateCriteria<Paged>()
.Add(Restrictions.Like("Id", search + '%'))
.Add(Restrictions.Ge("Row", match.Row))
.AddOrder(Order.Asc("Id"))
.SetMaxResults(rows)
.List<Paged>();
}