Send SqlQuery in Azure Function's DocumentDB Attribute - azure

I have an Azure Function that uses the DocumentDB attribute to connect to Cosmos DB. I'm using the Azure Functions for Visual Studio 2017 tooling. Here is the simple Function
[FunctionName("DocumentDbGet")]
public static HttpResponseMessage Run([HttpTrigger(AuthorizationLevel.Function, "get")]HttpRequestMessage req, TraceWriter log,
[DocumentDB("FunctionJunctionDemo", "Demo")]IEnumerable<DemoModel> items)
{
//Can perform operations on the "items" variable, which will be the result of the table/collection you specify
string name = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, "name", true) == 0)
.Value;
var item = items.Where(x => x.FirstName == name);
return req.CreateResponse(HttpStatusCode.OK);
}
I want to be able to pass a SqlQuery as one of the parameters of the DocumentDB attribute like so:
[FunctionName("DocumentDbGet")]
public static HttpResponseMessage Run([HttpTrigger(AuthorizationLevel.Function, "get")]HttpRequestMessage req, TraceWriter log,
[DocumentDB("FunctionJunctionDemo", "Demo", SqlQuery = $"select * from c where c.Id = {Id}")]IEnumerable<DemoModel> items)
{
//Can perform operations on the "items" variable, which will be the result of the table/collection you specify
string name = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, "name", true) == 0)
.Value;
var item = items.Where(x => x.FirstName == name);
return req.CreateResponse(HttpStatusCode.OK);
}
I've only seen 1 example do this and reported it was supposedly working. https://github.com/Azure/Azure-Functions/issues/271
The "error" I receive is it doesn't recognize anything named SqlQuery as a possible parameter.
I have looked at the documentation for Azure Function Input bindings
https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-documentdb#input-sample-with-multiple-documents which shows an output in the function.json file containing a sqlQuery attribute. How did that get in there?
If it isn't possible to pass in a SqlQuery in the DocumentDB attribute, what would be the best practice to filter the results up front to avoid returning an entire collection and then running it through a LINQ query?

You need to reference 1.1.0-beta version of Microsoft.Azure.WebJobs.Extensions.DocumentDB NuGet package (or later).
In that version SqlQuery is a valid parameter of DocumentDB attribute. You code compiles for me, if I remove $ sign before select string:
[DocumentDB("FunctionJunctionDemo", "Demo", SqlQuery = "select * from c where c.Id = {Id}")]
You don't need $ - it's used for string interpolation in C#, not something you want to do here.

Related

The property ETag not found in Document Db result returned using FeedResponse<Dynamic>

I have two simple queries, one returns Single Result using Id
Document doc = client.CreateDocumentQuery<Document>(entity.CollectionSelfLink,
new FeedOptions()
{
PartitionKey = new PartitionKey(entity.PartitionKey),
}).Where(d => d.Id == entity.Id).AsEnumerable().FirstOrDefault();
once I have the document, I simply call doc.ETag and it provides the ETag.
Next, I use the FeedResponse<dynamic> to get few results.
IDocumentQuery<Document> queryList = client.CreateDocumentQuery<Document>(collectionSelfLink, (SqlQuerySpec)query, new FeedOptions
{
PartitionKey = new PartitionKey(partitionKey),
MaxItemCount = 10,
RequestContinuation = requestContinuation,
}).AsDocumentQuery();
if (queryList.HasMoreResults)
{
FeedResponse<T> feed = await queryList.ExecuteNextAsync<T>();
IList<T> documents = feed.ToList();
}
Now, If I use the same doc.ETag I get the following error: That ETag is not found and it through DocumentDbException
oft.Azure.Documents.QueryResult.GetProperty(String propertyName, Type returnType)\r\n at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0)\r\n
The Documents returned by the SingleEntity and Query is exactly the same. Any thoughts what would cause this difference?
This line FeedResponse<T> feed = await queryList.ExecuteNextAsync<T>(); should be FeedResponse<Document> feed = await queryList.ExecuteNextAsync<Document>();

How to search records in all directory except one in Marklogic

I am trying to search XML's in my database using marklogic-java api with following restrictions :
1) A particular XML tag must contain my given value for ex : tradeId must equal to what I pass
2) Results must lie in collections provided by me
3) Results can lie anywhere in marklogic database except in one particular directory
I don't have the solution to point 3. Few of the records meeting my first two criteria have a URI starting with /TRADES/* and so i want to search everywhere except under directory "/TRADES".
This is how my code looks :
public static List<DocumentPage> getResults() throws KeyManagementException, NoSuchAlgorithmException,
KeyStoreException, CertificateException, IOException {
String tradeId = "XYZ";
DatabaseClient client = getDBClient();
QueryManager queryMgr = client.newQueryManager();
XMLDocumentManager xmlMngr = client.newXMLDocumentManager();
StringHandle rawHandle = new StringHandle();
rawHandle.withFormat(Format.XML).set(getQueryToFetchMessagesByTradeId(tradeId));
RawQueryByExampleDefinition querydef = queryMgr.newRawQueryByExampleDefinition(rawHandle);
querydef.setCollections("/messages/processed", /messages/toBeProcessed");
querydef.setDirectory("/");
return getDocumentPageList(querydef, client);
}
private static String getQueryToFetchMessagesByTradeId(String tradeId) {
String query = "<q:qbe xmlns:q=\"http://marklogic.com/appservices/querybyexample\">\n" + "<q:query>\n<q:word>"
+ "<tradeId tradeIdScheme=" + "\"http://www.abcd.com/internalid/trade-id\"" + ">" + tradeId
+ "</tradeId></q:word>\n" + "</q:query>\n" + "</q:qbe>";
return query;
}
Any help is highly appreciated.
I don't see an option to compose metadata (directory) queries in query by example syntax. So you'll have to use Structured Queries instead. I find them more readable in Java anyway since they don't require string concatenation. Use StructuredQueryBuilder like so:
StructuredQueryBuilder qb = new StructuredQueryBuilder();
StructuredQueryDefinition querydef =
qb.and(
qb.containerQuery(
qb.element(new QName("http://www.abcd.com/internalid/trade-id", "tradeId")),
qb.and(
qb.term( tradeId ),
qb.value(
qb.elementAttribute(
qb.element(new QName("http://www.abcd.com/internalid/trade-id", "tradeId")),
qb.attribute("tradeIdScheme")
),
"http://www.abcd.com/internalid/trade-id"
)
)
),
qb.not(
qb.directory(1, "/TRADES/")
)
);
querydef.setCollections("/messages/processed", "/messages/toBeProcessed")
return getDocumentPageList(querydef, client);

Querying DocumentDb in .NET with a parametrized query

For an application I'm running a query on DocumentDb in .NET. For this used I wanted to use a parametrized query, like this:
var sqlString = "select p.Id, p.ActionType, p.Type, p.Region, a.TimeStamp, a.Action from History p join a in p.Actions where a.TimeStamp >= #StartTime and a.TimeStamp <= #EndTime and p.ClientId = #ClientId and p.ActionType = #ActionType";
if (actionType != "") { sqlString += actionTypeFilter; }
var queryObject = new SqlQuerySpec
{
QueryText = sqlString,
Parameters = new SqlParameterCollection()
{
new SqlParameter("#StartTime", startDate),
new SqlParameter("#EndTime", endDate),
new SqlParameter("#ClientId", clientId.ToString()),
new SqlParameter("#ActionType", actionType)
},
};
var dataListing = _documentDbClient.CreateDocumentQuery<PnrTransaction>(UriToPnrHistories, queryObject, new FeedOptions() { MaxItemCount = 1 });
When I execute this, I'm getting en empty dataset. But when I use the same query, and build it using classic string replace it works just fine.
Can anyone tell me what I'm doing wrong in my parametrized query?
If the code above is the running code, you still add the actiontypeFilter on the parameterized SQL string. Try to remove the if statement on line 2. Seems to me that may be your problem.
It would help if you could post a sample document from the server.
I usually see this syntax:
SqlParameterCollection parameters = new SqlParameterCollection();
parameters.Add(...);
parameters.Add(...);
Try that and see if you get different results. It might be that the list you use to initialize it in your answer needs to be typed differently to work.

Exception in OrmLite: Must declare the scalar variable

Our code has a SqlExpression, which at its bare minimum is something like:
var q = db.From<Users>();
q.Where(u => u.Age == 25);
totalRecords = db.Scalar<int>(q.ToCountStatement());
q.ToCountStatement() generates the following query:
SELECT COUNT(*) FROM "Users" WHERE ("Age" = #0)
However, db.Scalar() throws an exception: Must declare the scalar variable "#0". This has started occurring in recent versions (tested in 4.0.54). The same code was working fine until v4.0.50. I've checked the release notes, but couldn't find a related change.
Even passing a parameter throws the same exception:
totalRecords = db.Scalar<int>(q.ToCountStatement(), 25);
Is it a bug, or my oversight?
Secondly, is it possible to get q.ToCountStatement() to generate a more optimized query with COUNT(Age) or COUNT([PrimaryKey]) instead of COUNT(*)?
Now that OrmLite defaults to parameterized queries you also need to provide the queries db parameters when executing a query (if you've specified any params), e.g:
var q = db.From<Users>().Where(u => u.Age == 25);
var count = db.Scalar<int>(q.ToCountStatement(), q.Params);
You can also use OrmLite's explicit Count() API's, e.g:
db.Count<User>(x => x.Age == 25);
Or with a typed SqlExpression:
var q = db.From<User>().Where(x => x.Age == 25);
db.Count(q);
Otherwise another way to specify db params is to use an anonymous object, e.g:
db.Scalar<int>("SELECT COUNT(*) FROM Users WHERE Age=#age", new { age = 25});

Reconstructing an ODataQueryOptions object and GetInlineCount returning null

In an odata webapi call which returns a PageResult I extract the requestUri from the method parameter, manipulate the filter terms and then construct a new ODataQueryOptions object using the new uri.
(The PageResult methodology is based on this post:
http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options )
Here is the raw inbound uri which includes %24inlinecount=allpages
http://localhost:59459/api/apiOrders/?%24filter=OrderStatusName+eq+'Started'&filterLogic=AND&%24skip=0&%24top=10&%24inlinecount=allpages&_=1376341370337
Everything works fine in terms of the data returned except Request.GetInLineCount returns null.
This 'kills' paging on the client side as the client ui elements don't know the total number of records.
There must be something wrong with how I'm constructing the new ODataQueryOptions object.
Please see my code below. Any help would be appreciated.
I suspect this post may contain some clues https://stackoverflow.com/a/16361875/1433194 but I'm stumped.
public PageResult<OrderVm> Get(ODataQueryOptions<OrderVm> options)
{
var incomingUri = options.Request.RequestUri.AbsoluteUri;
//manipulate the uri here to suit the entity model
//(related to a transformation needed for enumerable type OrderStatusId )
//e.g. the query string may include %24filter=OrderStatusName+eq+'Started'
//I manipulate this to %24filter=OrderStatusId+eq+'Started'
ODataQueryOptions<OrderVm> options2;
var newUri = incomingUri; //pretend it was manipulated as above
//Reconstruct the ODataQueryOptions with the modified Uri
var request = new HttpRequestMessage(HttpMethod.Get, newUri);
//construct a new options object using the new request object
options2 = new ODataQueryOptions<OrderVm>(options.Context, request);
//Extract a queryable from the repository. contents is an IQueryable<Order>
var contents = _unitOfWork.OrderRepository.Get(null, o => o.OrderByDescending(c => c.OrderId), "");
//project it onto the view model to be used in a grid for display purposes
//the following projections etc work fine and do not interfere with GetInlineCount if
//I avoid the step of constructing and using a new options object
var ds = contents.Select(o => new OrderVm
{
OrderId = o.OrderId,
OrderCode = o.OrderCode,
CustomerId = o.CustomerId,
AmountCharged = o.AmountCharged,
CustomerName = o.Customer.FirstName + " " + o.Customer.LastName,
Donation = o.Donation,
OrderDate = o.OrderDate,
OrderStatusId = o.StatusId,
OrderStatusName = ""
});
//note the use of 'options2' here replacing the original 'options'
var settings = new ODataQuerySettings()
{
PageSize = options2.Top != null ? options2.Top.Value : 5
};
//apply the odata transformation
//note the use of 'options2' here replacing the original 'options'
IQueryable results = options2.ApplyTo(ds, settings);
//Update the field containing the string representation of the enum
foreach (OrderVm row in results)
{
row.OrderStatusName = row.OrderStatusId.ToString();
}
//get the total number of records in the result set
//THIS RETURNS NULL WHEN USING the 'options2' object - THIS IS MY PROBLEM
var count = Request.GetInlineCount();
//create the PageResult object
var pr = new PageResult<OrderVm>(
results as IEnumerable<OrderVm>,
Request.GetNextPageLink(),
count
);
return pr;
}
EDIT
So the corrected code should read
//create the PageResult object
var pr = new PageResult<OrderVm>(
results as IEnumerable<OrderVm>,
request.GetNextPageLink(),
request.GetInlineCount();
);
return pr;
EDIT
Avoided the need for a string transformation of the enum in the controller method by applying a Json transformation to the OrderStatusId property (an enum) of the OrderVm class
[JsonConverter(typeof(StringEnumConverter))]
public OrderStatus OrderStatusId { get; set; }
This does away with the foreach loop.
InlineCount would be present only when the client asks for it through the $inlinecount query option.
In your modify uri logic add the query option $inlinecount=allpages if it is not already present.
Also, there is a minor bug in your code. The new ODataQueryOptions you are creating uses a new request where as in the GetInlineCount call, you are using the old Request. They are not the same.
It should be,
var count = request.GetInlineCount(); // use the new request that your created, as that is what you applied the query to.

Resources