How to Execute an Existing Stored Procedure in DocumentDB? - azure

I want to create a stored procedure in DocumentDB and use it whenever I need later on. To execute a stored procedure I need to know a storedProcedureLink. When I register stored procedure with CreateStoredProcedureAsync method I get the link, am I supposed to store this link somewhere myself if I want to execute this stored procedure later? Can I execute a stored procedure if all I know is a procedure name? From all the examples I found it seems that I need to create and register stored procedure right before I need to execute it, is it the case?

You can absolutely create the Stored Procedure beforehand and have it saved on the server for future use. You do not need to create it just before using it. This was for sample purposes only. We expect the majority of the time that the stored procedures will exist already and you just use them in your application.
You do need the SelfLink to execute the Stored Procedure.
One way to get this would be to query for the Stored Procedure (by Name), get the SelfLink and then use that to execute the stored procedure.
Querying for a Stored Procedure by name would look something like;
StoredProcedure sproc = client.CreateStoredProcedureQuery(collection.StoredProceduresLink, "select * from root r where r.id = \"sproc name\"");
The resulting sproc variable will contain the SelfLink of the stored procedure you want to execute.
var result = client.ExecuteStoredProcedureAsync(sproc.SelfLink);
For comprehensive samples of how to work with Stored Procedures please see the DocumentDB.Samples.ServerSideScripts project in the samples posted at;
http://code.msdn.microsoft.com/Azure-DocumentDB-NET-Code-6b3da8af

From all the examples I found it seems that I need to create and register stored procedure right before I need to execute it, is it the case?
No. Stored procedure creation and registration does not have to happen during execution.
The samples are educational, but do not offer real-world patterns. See the DocumentManagement basic CRUD operations. Samples also assume a single collection too. See the Partitioning basic CRUD operations.
While I applaud the DocumentDB adaption of SQL coding conventions, the usage pattern currently falls short for .NET developers. The decade old pattern of creating CRUD stored procedures in SQL Server and then calling them by name via ADO.NET or a TableAdapter in a DataSet does not work in the DocumentDB.
Can I execute a stored procedure if all I know is a procedure name?
Yes, but it's not pretty:
StoredProcedure storedProcedure = this.DocumentClient.CreateStoredProcedureQuery(new Uri(collection.StoredProceduresLink)).Where(p => p.Id == "GetPunkRocker").AsEnumerable().FirstOrDefault();
When using a PartitionResolver, things get more complicated:
public async Task<PunkRocker> GetPunkRockerAsync(string partitionKey)
{
foreach (string collectionLink in this.PartitionResolver.ResolveForRead(partitionKey))
{
DocumentCollection collection = this.DocumentClient.CreateDocumentCollectionQuery(new Uri(this.Database.SelfLink)).Where(c => c.SelfLink == collectionLink).AsEnumerable().FirstOrDefault();
if (collection == null)
{
// Log...
continue;
}
StoredProcedure storedProcedure = this.DocumentClient.CreateStoredProcedureQuery(new Uri(collection.StoredProceduresLink)).Where(p => p.Id == "GetPunkRocker").AsEnumerable().FirstOrDefault();
if (storedProcedure == null)
{
// Log...
continue;
}
PunkRocker punkRocker = await this.DocumentClient.ExecuteStoredProcedureAsync<PunkRocker>(new Uri(storedProcedure.SelfLink), partitionKey);
if (punkRocker != null)
{
return punkRocker;
}
}
return null;
}

Just tried this approach and it does not work.
client.CreateStoredProcedureQuery( link, String.Format( "select * from root r where r.id = '{0}'", "spname1" ) ).ToList( ).FirstOrDefault( );
returns null
client.CreateStoredProcedureQuery( link, String.Format( "select * from root r" ) ).ToList( ).FirstOrDefault( );
returns correct stored procedure
{
"id": "spname1",
"body": "function() {
var context = getContext();
var collection = context.getCollection();
}",
"_rid": "XXX",
"_ts": 1410449011.0,
"_self": "XXX",
"_etag": "XXX"
}

You can look for the SP in the DB first in .NET SDK
StoredProcedure storedProcedure = Client.CreateStoredProcedureQuery(GetCollection(eColl).SelfLink).Where(c => c.Id == "SP name").AsEnumerable().FirstOrDefault();

It appears that Microsoft is now providing a few samples on how to execute stored procedures:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.documents.client.documentclient.executestoredprocedureasync?view=azure-dotnet

Related

Changing a value in an Azure Cosmos DB

I've inherited a project at work that uses Azure Cosmos DB. It's completely new to me. In the CosmosDB, we have a bunch of user preferences that are saved. I've discovered a typo in the settings that I need to fix. However, I cannot figure out how to modify the value.
So far I've found the query explorer and I want to run this query:
Update c
set c.Setting = REPLACE(c.Setting, 'N*m', 'N-m')
but query explorer only supports select, not update.
I tried to use Azure Storage Explorer, but when I try to access the document I get nothing except a modal saying "Hold on! We are still working on this." Seriously Microsoft?
My current thinking is to upload a stored procedure and run that. But I'm not sure where to start. My other thinking is to write a small c# application that iterates through each user document and updates them individually. Something like this:
currId = 0;
databaseId = ...;
collectionId = ...;
collectionLink = ...;
while (currId < maxUserId) {
var response = await client.ReadDocumentAsync(UriFactory.CreateDocumentUri(databaseId, collectionId, currId.ToString()));
if (response.Resource != null) {
var upserted = response.Resource;
upserted.SetPropertyValue("Setting", "N-m");
response = await client.UpsertDocumentAsync(collectionLink, upserted);
}
currId++;
}
But boy if that doesn't seem like a dumb idea...
What's the best way to update a single value in a CosmosDB Document?

Azure Storage Table Does not return whole partition

I found some situation on production when
CloudContext.TableData.Where( A => A.PartitionKey == "MYKEY").ToList();
where TableData is
public DataServiceQuery<T> TableData { get { return CreateQuery<T>( _TableName ); } }
does not return the whole partition (I have less than 1000 records there).
In my case it returns 367 records while in VS2010 Server Explorer or in Azure Storage Explorer I get 414 records (condition is the same).
Did anyone experience the same problem?
Also If I change the query and add RowKey into the condition - I get required record with no problem.
You have to better understand the Table Service. In the official documentation here there are listed other conditions which affect number of records returned. If you want to retrieve the whole partition you have to inspect the TableResult for Continuation Token and use provided continuation token to execute the same query over and over again, until all the results come.
You can use an approach similar to the following:
private IEnumerable<MyEntityType> GetAllEntities()
{
var result = this._tables.GetSegmentedEntities(100, null); // null is for continuation token
while (result.Results.Count > 0)
{
foreach (var ufs in result.Results)
{
yield return new MyEntityType(ufs.RowKey, ufs.WhateverOtherPropertyINeed);
}
if (result.ContinuationToken != null)
{
result = this._tables.GetSegmentedEntities(100, result.ContinuationToken);
}
else
{
break;
}
}
}
Where GetSegmentedEntities(100, result.ContinuationToken) is defined as:
public TableQuerySegment<MyEntityType> GetSegmentedEntities(int pageSize, TableContinuationToken token)
{
var partKey = "My_Desired_Partition_key_passed_via_Const_or_method_Param";
TableQuery<MyEntityType> query = new TableQuery<MyEntityType>()
.Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partKey));
query.TakeCount = pageSize;
return this.azureTableReference.ExecuteQuerySegmented<MyEntityType>(query, token);
}
You can use and modify this code for your case.
This is a known and documented behavior. The Table service API will either return 1000 entities or as much entities as possible within 5 seconds. If the query takes longer than 5 seconds to execute, it'll return a continuation token.
With the addition of rowkey you are making the query more specific and hence faster and as a result yo are getting all the entities.
See TimeOuts and Pagination on MSDN for details
If you are getting partial result sets then there will be two factors.
i) You are having more than 1000 records matching the filter
ii) Querying took more than 5 seconds.
iii) Query crosses partition boundary.
As you are having less than 1000 records the first factor wont be a issue.And as you are retrieving based on PartitionKey equality third one also wont cause any problem. You are facing this problem because of second factor.
Two handle this you need to work on continuation token. You can refer this link for more info.

Call stored procedure with optional parameters using OrmLite

I am using OrmLite to call stored procedure that has optional parameters.
_dbConnection.SqlList<CustomerDTO>("sp_getcustomers #name", new { name = request.Name });
This statement is generating dynamic sql statement with #name as parameter. But I am not knowing how to pass null to this parameter, I tried using DBNull.Value but its not working.
Exception : The given key was not present in the dictionary is raised.
_dbConnection.SqlList<CustomerDTO>("sp_getcustomers #name", new { name = request.Name ?? System.Data.SqlTypes.SqlString.Null});
See these SqlProviderTests for examples of how to effectively make use of OrmLite's Sql* apis.
The right way to call it is with something like:
Db.SqlList<CustomerDTO>("EXEC sp_getcustomers #Name", new { request.Name });
Ormlite has a T4 file to generate C# functions equivalents for the SPs (for SqlServer); the generated files allows you to pass null values.
Support for null parameters in stored procedures was added in commit e6ef83a and released with version 3.9.56 of ServiceStack.OrmLite.

Add or replace entity in Azure Table Storage

I'm working with Windows Azure Table Storage and have a simple requirement: add a new row, overwriting any existing row with that PartitionKey/RowKey. However, saving the changes always throws an exception, even if I pass in the ReplaceOnUpdate option:
tableServiceContext.AddObject(TableName, entity);
tableServiceContext.SaveChangesWithRetries(SaveChangesOptions.ReplaceOnUpdate);
If the entity already exists it throws:
System.Data.Services.Client.DataServiceRequestException: An error occurred while processing this request. ---> System.Data.Services.Client.DataServiceClientException: <?xml version="1.0" encoding="utf-8" standalone="yes"?>
<error xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<code>EntityAlreadyExists</code>
<message xml:lang="en-AU">The specified entity already exists.</message>
</error>
Do I really have to manually query for the existing row first and call DeleteObject on it? That seems very slow. Surely there is a better way?
As you've found, you can't just add another item that has the same row key and partition key, so you will need to run a query to check to see if the item already exists. In situations like this I find it helpful to look at the Azure REST API documentation to see what is available to the storage client library. You'll see that there are separate methods for inserting and updating. The ReplaceOnUpdate only has an effect when you're updating, not inserting.
While you could delete the existing item and then add the new one, you could just update the existing one (saving you one round trip to storage). Your code might look something like this:
var existsQuery = from e
in tableServiceContext.CreateQuery<MyEntity>(TableName)
where
e.PartitionKey == objectToUpsert.PartitionKey
&& e.RowKey == objectToUpsert.RowKey
select e;
MyEntity existingObject = existsQuery.FirstOrDefault();
if (existingObject == null)
{
tableServiceContext.AddObject(TableName, objectToUpsert);
}
else
{
existingObject.Property1 = objectToUpsert.Property1;
existingObject.Property2 = objectToUpsert.Property2;
tableServiceContext.UpdateObject(existingObject);
}
tableServiceContext.SaveChangesWithRetries(SaveChangesOptions.ReplaceOnUpdate);
EDIT: While correct at the time of writing, with the September 2011 update Microsoft have updated the Azure table API to include two upsert commands, Insert or Replace Entity and Insert or Merge Entity
In order to operate on an existing object NOT managed by the TableContext with either Delete or SaveChanges with ReplaceOnUpdate options, you need to call AttachTo and attach the object to the TableContext, instead of calling AddObject which instructs TableContext to attempt to insert it.
http://msdn.microsoft.com/en-us/library/system.data.services.client.dataservicecontext.attachto.aspx
in my case it was not allowed to remove it first, thus I do it like this, this will result in one transaction to server which will first remove existing object and than add new one, removing need to copy property values
var existing = from e in _ServiceContext.AgentTable
where e.PartitionKey == item.PartitionKey
&& e.RowKey == item.RowKey
select e;
_ServiceContext.IgnoreResourceNotFoundException = true;
var existingObject = existing.FirstOrDefault();
if (existingObject != null)
{
_ServiceContext.DeleteObject(existingObject);
}
_ServiceContext.AddObject(AgentConfigTableServiceContext.AgetnConfigTableName, item);
_ServiceContext.SaveChangesWithRetries();
_ServiceContext.IgnoreResourceNotFoundException = false;
Insert/Merge or Update was added to the API in September 2011. Here is an example using the Storage API 2.0 which is easier to understand then the way it is done in the 1.7 api and earlier.
public void InsertOrReplace(ITableEntity entity)
{
retryPolicy.ExecuteAction(
() =>
{
try
{
TableOperation operation = TableOperation.InsertOrReplace(entity);
cloudTable.Execute(operation);
}
catch (StorageException e)
{
string message = "InsertOrReplace entity failed.";
if (e.RequestInformation.HttpStatusCode == 404)
{
message += " Make sure the table is created.";
}
// do something with message
}
});
}
The Storage API does not allow more than one operation per entity (delete+insert) in a group transaction:
An entity can appear only once in the transaction, and only one operation may be performed against it.
see MSDN: Performing Entity Group Transactions
So in fact you need to read first and decide on insert or update.
You may use UpsertEntity and UpsertEntityAsync methods in the official Microsoft Azure.Data.Tables TableClient.
The fully working example is available at https://github.com/Azure-Samples/msdocs-azure-data-tables-sdk-dotnet/blob/main/2-completed-app/AzureTablesDemoApplicaton/Services/TablesService.cs --
public void UpsertTableEntity(WeatherInputModel model)
{
TableEntity entity = new TableEntity();
entity.PartitionKey = model.StationName;
entity.RowKey = $"{model.ObservationDate} {model.ObservationTime}";
// The other values are added like a items to a dictionary
entity["Temperature"] = model.Temperature;
entity["Humidity"] = model.Humidity;
entity["Barometer"] = model.Barometer;
entity["WindDirection"] = model.WindDirection;
entity["WindSpeed"] = model.WindSpeed;
entity["Precipitation"] = model.Precipitation;
_tableClient.UpsertEntity(entity);
}

Why no SQL for NHibernate 3 Query?

Why is no SQL being generated when I run my Nhibernate 3 query?
public IQueryable<Chapter> FindAllChapters()
{
using (ISession session = NHibernateHelper.OpenSession())
{
var chapters = session.QueryOver<Chapter>().List();
return chapters.AsQueryable();
}
}
If I run the query below I can see that the SQL that gets created.
public IQueryable<Chapter> FindAllChapters()
{
using (ISession session = NHibernateHelper.OpenSession())
{
var resultDTOs = session.CreateSQLQuery("SELECT Title FROM Chapter")
.AddScalar("Title", NHibernateUtil.String)
.List();
// Convert resultDTOs into IQueryable<Chapter>
}
}
Linq to NHibernate (like Linq to entities) uses delayed execution. You are returning IQueryable<Chapter> which means that you might add further filtering before using the data, so no query is executed.
If you called .ToList() or .List() (i forget which is in the API), then it would actually produce data and execute the query.
In other words, right now you have an unexecuted query.
Added: Also use Query() not QueryOver(). QueryOver is like detached criteria.
For more info, google "delayed execution linq" for articles like this

Resources