Access resources by Id in Azure DocumentDB - azure

I just started playing with Azure DocumentDB and my excitement has turned into confusion. This thing is weird. It seems like everything (databases, collections, documents) needs to be accessed not by its id, but by its 'SelfLink'. For example:
I create a database:
public void CreateDatabase()
{
using (var client = new DocumentClient(new Uri(endpoint), authKey))
{
Database db = new Database()
{
Id = "TestDB",
};
client.CreateDatabaseAsync(db).Wait();
}
}
Then later sometime I want to create a Collection:
public void CreateCollection()
{
using (var client = new DocumentClient(new Uri(endpoint), authKey))
{
DocumentCollection collection = new DocumentCollection()
{
Id = "TestCollection",
};
client.CreateDocumentCollectionAsync(databaseLink: "???", documentCollection: collection).Wait();
}
}
The api wants a 'databaseLink' when what I'd really prefer to give it is my database Id. I don't have the 'databaseLink' handy. Does DocumentDB really expect me to pull down a list of all databases and go searching through it for the databaseLink everytime I want to do anything?
This problem goes all the way down. I can't save a document to a collection without having the collection's 'link'.
public void CreateDocument()
{
using (var client = new DocumentClient(new Uri(endpoint), authKey))
{
client.CreateDocumentAsync(documentCollectionLink: "???", document: new { Name = "TestName" }).Wait();
}
}
So to save a document I need the collection's link. To get the collections link I need the database link. To get the database link I have to pull down a list of all databases in my account and go sifting through it. Then I have to use that database link that I found to pull down a list of collections in that database that I then have to sift through looking for the link of the collection I want. This doesn't seem right.
Am I missing something? Am I not understanding how to use this? Why am I assigning ids to all my resources when DocumentDB insists on using its own link scheme to identify everything? My question is 'how do I access DocumentDB resources by their Id?'

The information posted in other answers from 2014 is now somewhat out of date. Direct addressing by Id is possible:
Although _selflinks still exist, and can be used to access resources, Microsoft have since added a much simpler way to locate resources by their Ids that does not require you to retain the _selflink :
UriFactory
UriFactory.CreateDocumentCollectionUri(databaseId, collectionId))
UriFactory.CreateDocumentUri(databaseId, collectionId, "document id");
This enables you to create a safe Uri (allowing for example for whitespace) - which is functionally identical to the resources _selflink; the example given in the Microsoft announcement is shown below:
// Use **UriFactory** to build the DocumentLink
Uri docUri = UriFactory.CreateDocumentUri("SalesDb", "Catalog", "prd123");
// Use this constructed Uri to delete the document
await client.DeleteDocumentAsync(docUri);
The announcement, from August 13th 2015, can be found here:
https://azure.microsoft.com/en-us/blog/azure-documentdb-bids-fond-farewell-to-self-links/

I would recommend you look at the code samples here in particular the DocumentDB.Samples.ServerSideScripts project.
In the Program.cs you will find the GetOrCreateDatabaseAsync method:
/// <summary>
/// Get or create a Database by id
/// </summary>
/// <param name="id">The id of the Database to search for, or create.</param>
/// <returns>The matched, or created, Database object</returns>
private static async Task<Database> GetOrCreateDatabaseAsync(string id)
{
Database database = client.CreateDatabaseQuery()
.Where(db => db.Id == id).ToArray().FirstOrDefault();
if (database == null)
{
database = await client.CreateDatabaseAsync(
new Database { Id = id });
}
return database;
}
To answer you question, you can use this method to find your database by its id and other resources (collections, documents etc.) using their respective Create[ResourceType]Query() methods.
Hope that helps.

The create database call returns a the database object:
var database = client.CreateDatabaseAsync(new Database { Id = databaseName }).Result.Resource;
And then you can use that to create your collection
var spec = new DocumentCollection { Id = collectionName };
spec.IndexingPolicy.IndexingMode = IndexingMode.Consistent;
spec.IndexingPolicy.Automatic = true;
spec.IndexingPolicy.IncludedPaths.Add(new IndexingPath { IndexType = IndexType.Range, NumericPrecision = 6, Path = "/" });
var options = new RequestOptions
{
ConsistencyLevel = ConsistencyLevel.Session
};
var collection = client.CreateDocumentCollectionAsync(database.SelfLink, spec, options).Result.Resource;

The client.Create... methods return the objects which have the self links you are looking for
Database database = await client.CreateDatabaseAsync(
new Database { Id = "Foo"});
DocumentCollection collection = await client.CreateDocumentCollectionAsync(
database.SelfLink, new DocumentCollection { Id = "Bar" });
Document document = await client.CreateDocumentAsync(
collection.SelfLink, new { property1 = "Hello World" });

For deleting the document in partitioned collection, please leverage this format:
result = await client.DeleteDocumentAsync(selfLink, new RequestOptions {
PartitionKey = new PartitionKey(partitionKey)
});

Related

Acumatica GetList error: Optimization cannot be performed.The following fields cause the error: Attributes.AttributeID

Developer's version of Acumatica 2020R1 is installed locally. Data for sample tenant MyTenant from training for I-300 were loaded, and WSDL connection established.
DefaultSoapClient is created fine.
However, attempts to export any data by using Getlist cause errors:
using (Default.DefaultSoapClient soapClient =
new Default.DefaultSoapClient())
{
//Sign in to Acumatica ERP
soapClient.Login
(
"Admin",
"*",
"MyTenant",
"Yogifon",
null
);
try
{
//Retrieving the list of customers with contacts
//InitialDataRetrieval.RetrieveListOfCustomers(soapClient);
//Retrieving the list of stock items modified within the past day
// RetrievalOfDelta.ExportStockItems(soapClient);
RetrievalOfDelta.ExportItemClass(soapClient);
}
public static void ExportItemClass(DefaultSoapClient soapClient)
{
Console.WriteLine("Retrieving the list of item classes...");
ItemClass ItemClassToBeFound = new ItemClass
{
ReturnBehavior = ReturnBehavior.All,
};
Entity[] ItemClasses = soapClient.GetList(ItemClassToBeFound);
string lcItemType = "", lcValuationMethod = "";
int lnCustomFieldsCount;
using (StreamWriter file = new StreamWriter("ItemClass.csv"))
{
//Write the values for each item
foreach (ItemClass loItemClass in ItemClasses)
{
file.WriteLine(loItemClass.Note);
}
}
The Acumatica instance was modified by adding a custom field to Stock Items using DAC, and by adding several Attributes to Customer and Stock Items.
Interesting enough, this code used to work until something broke it.
What is wrong here?
Thank you.
Alexander
In the request you have the following line: ReturnBehavior = ReturnBehavior.All
That means that you try to retrieve all linked/detail entities of the object. Unfortunately, some object are not optimized enough to not affect query performance in GetList scenarios.
So, you have to options:
Replace ReturnBehavior=All by explicitly specifying linked/detail entities that you want to retrieve and not include Attributes into the list.
Retrieve StockItem with attributes one by one using Get operation instead of GetList.
P.S. The problem with attributes will most likely be fixed in the next version of API endpoint.
Edit:
Code sample for Get:
public static void ExportItemClass(DefaultSoapClient soapClient)
{
Console.WriteLine("Retrieving the list of item classes...");
ItemClass ItemClassToBeFound = new ItemClass
{
ReturnBehavior = ReturnBehavior.Default //retrieve only default fields (without attributes and other linked/detailed entities)
};
Entity[] ItemClasses = soapClient.GetList(ItemClassToBeFound);
foreach(var entity in ItemClasses)
{
ItemClass itemClass= entity as ItemClass;
ItemClass.ReturnBehavior=ReturnBehavior.All;
// retrieve each ItemClass with all the details/linked entities individually
ItemClass retrievedItemCLass = soapClient.Get(itemClass);
}

CosmosDb Delete in Loop fails after one deletion

I have created a console app to delete documents from CosmosDB based on a partition that we have set up.
using (var client = new DocumentClient(new Uri(Config["CosmosEndpoint"]), Config["CosmosAuthkey"]))
{
var db = Options.Env.ToUpper().Equals("CI") ? Config["CiDatabase"] : Config["QaDatabase"];
var tenantString = $"{Options.Tenant}-{Options.Language}";
Log.Information($"Deleting {tenantString} from {db}-{Config["CosmosCollection"]}");
var query = client.CreateDocumentQuery<Document>(
UriFactory.CreateDocumentCollectionUri(db, Config["CosmosCollection"]),
"SELECT * FROM c",
new FeedOptions()
{
PartitionKey = new PartitionKey(tenantString)
}
).ToList();
Log.Information($"Found {query.Count} records for Tenant: {tenantString}");
if (query.Count > 0)
{
foreach (var doc in query)
{
Log.Information($"Deleting document {doc.Id}");
await client.DeleteDocumentAsync(doc.SelfLink,
new RequestOptions { PartitionKey = new PartitionKey(tenantString) });
Log.Information($"Deleted document {doc.Id}");
}
}
else
{
Log.Information($"Tenant: {tenantString}, No Search Records Found");
}
}
This never reaches the line Log.Information($"Deleted document {doc.Id}"); but also does not seem to throw an exception. I have wrapped the call in a try/catch for DocumentClient/ArgumentNull Exception in an attempt to see what it was but it just bombs on the delete call. It does, however, always delete one document.
This tells me that my config must be correct as I am connecting and querying and even deleting but not for all documents in the query. Even more strange is that I have copied this from another application that I wrote earlier where this code works.
Is there an upper limit on connecting meaning I need to delay my loop
some?
Why do I not see an exception when using a try/catch?
Or is there another reason that I only seem to be able to delete one document at a time with this code?

access indexfields, batchfields and batch variables in custom module

In my setup form I configure some settings for my custom module. The settings are stored in the custom storage of the batch class. Given the variable IBatchClass batchClass I can access the data by executing
string data = batchClass.get_CustomStorageString("myKey");
and set the data by executing
batchClass.set_CustomStorageString("myKey", "myValue");
When the custom module gets executed I want to access this data from the storage. The value I get returned is the key for the batchfield collection or indexfield collection or batch variables collection. When creating Kofax Export Connector scripts I would have access to the ReleaseSetupData object holding these collections.
Is it possible to access these fields during runtime?
private string GetFieldValue(string fieldName)
{
string fieldValue = string.Empty;
try
{
IIndexFields indexFields = null; // access them
fieldValue = indexFields[fieldName].ToString();
}
catch (Exception e)
{
}
try
{
IBatchFields batchFields = null; // access them
fieldValue = batchFields[fieldName].ToString();
}
catch (Exception e)
{
}
try
{
dynamic batchVariables = null; // access them
fieldValue = batchVariables[fieldName].ToString();
}
catch (Exception e)
{
}
return fieldValue;
}
The format contains a string like
"{#Charge}; {Current Date} {Current Time}; Scan Operator: {Scan
Operator's User ID}; Page: x/y"
and each field wrapped by {...} represents a field from one of these 3 collections.
Kofax exposes a batch as an XML, and DBLite is basically a wrapper for said XML. The structure is explained in AcBatch.htm and AcDocs.htm (to be found under the CaptureSV directory). Here's the basic idea (just documents are shown):
AscentCaptureRuntime
Batch
Documents
Document
For a standard server installation, the file would be located here: \\servername\CaptureSV\AcBatch.htm. A single document has child elements itself such as index fields, and multiple properties such as Confidence, FormTypeName, and PDFGenerationFileName.
Here's how to extract the elements from the active batch (your IBatch instance) as well as accessing all batch fields:
var runtime = activeBatch.ExtractRuntimeACDataElement(0);
var batch = runtime.FindChildElementByName("Batch");
foreach (IACDataElement item in batch.FindChildElementByName("BatchFields").FindChildElementsByName("BatchField"))
{
}
The same is true for index fields. However, as those reside on document level, you would need to drill down to the Documents element first, and then retrieve all Document children. The following example accesses all index fields as well, storing them in a dictionary named IndexFields:
var documents = batch.FindChildElementByName("Documents").FindChildElementsByName("Document");
var indexFields = DocumendocumentstData.FindChildElementByName("IndexFields").FindChildElementsByName("IndexField");
foreach (IACDataElement indexField in indexFields)
{
IndexFields.Add(indexField["Name"], indexField["Value"]);
}
With regard to Batch Variables such as {Scan Operator's User ID}, I am not sure. Worst case scenario is to assign them as default values to index or batch fields.

Handling two upserts on the same object in cosmos db

Suppose I have a document with a 'count' field and I want to increase the count field everytime a function is called, something like:
container.items.upsert({id: "test", count:currentCount+1})
Where 'currentCount' was the lasts value received from the document with id 'test'.
If an async call is made to increment the count during another count (from the time between currentCount is retrieved and the upsert happens) the second async call will have the wrong data (i.e. the currentCount from before the first calls incrementing of the currentCount).
How would I go about preventing such a scenario?
How would I go about preventing such a scenario?
To prevent such scenarios, you should use Optimistic Concurrency. Essentially make use of document's ETag property to include in your upsert requests. When you fetch the document, you get it's ETag back. You need to include the same value in your upsert request. If the document has not changed on the server (i.e. the ETag value is the same), then the update operation will succeed otherwise it will fail.
From this blog post, here's the code sample:
using System;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Shouldly;
using Xunit;
namespace Demo
{
public class OptimtimisticConcurrencyTests
{
private readonly DocumentClient _client;
private const string EndpointUrl = "https://localhost:8081";
private const string AuthorizationKey = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
private const string DatabaseId = "ConcurrencyDemo";
private const string CollectionId = "Customers";
public OptimtimisticConcurrencyTests()
{
_client = new DocumentClient(new Uri(EndpointUrl), AuthorizationKey);
}
[Fact]
public async Task Should_Throw_With_PreconditionFailed()
{
// Setup our Database and add a new Customer
var dbSetup = new DatabaseSetup(_client);
await dbSetup.Init(DatabaseId, CollectionId);
var addCustomer = new Customer(Guid.NewGuid().ToString(), "Demo");
await dbSetup.AddCustomer(addCustomer);
// Fetch out the Document (Customer)
var document = (from f in dbSetup.Client.CreateDocumentQuery(dbSetup.Collection.SelfLink)
where f.Id == addCustomer.Id
select f).AsEnumerable().FirstOrDefault();
// Cast the Document to our Customer & make a data change
var editCustomer = (Customer) (dynamic) document;
editCustomer.Name = "Changed";
// Using Access Conditions gives us the ability to use the ETag from our fetched document for optimistic concurrency.
var ac = new AccessCondition {Condition = document.ETag, Type = AccessConditionType.IfMatch};
// Replace our document, which will succeed with the correct ETag
await dbSetup.Client.ReplaceDocumentAsync(document.SelfLink, editCustomer,
new RequestOptions {AccessCondition = ac});
// Replace again, which will fail since our (same) ETag is now invalid
var ex = await dbSetup.Client.ReplaceDocumentAsync(document.SelfLink, editCustomer,
new RequestOptions {AccessCondition = ac}).ShouldThrowAsync<DocumentClientException>();
ex.StatusCode.ShouldBe(HttpStatusCode.PreconditionFailed);
}
}
}

How to call Azure Actions in xamarin forms using Azure Service Provider

I am using Azure Service provider (Azure SDK in xamarin forms) to download data from Azure cloud Server, I am using bellow code to fetch all data
var table = AzureServiceProvider.Instance.GetRemoteTable<T>();
var query = table.CreateQuery();
if (filter != null)
{
query = table.Where(filter);
}
List<T> azureDatas;
await query.ToListAsync();
when I use code above it hits following URL https://MyService.azurewebsites.net/tables/TableName
But now I have to pass id (i.e api/table/{TableName}/{controller}/{id}) to fetch only required data for matching that that id
using above same code its hitting above URL
https://MyService.azurewebsites.net/tables/TableName
Inst ed of that I want to use
EX:-
https://mobilddevservice.azurewebsites.net/tables/TableName/(methodName)/(ID)10338654
I don't know if you figures this out yet, but to target a specific method in your controller you can use the ".WithParameters" method on your table.
So lets say you have 2 methods in your controller:
// GET tables/TableName/id
public SingleResult<TableName> GetDataFromName(string name)
{
//Your logic here
}
// GET tables/TableName/id
public SingleResult<TableName> GetDataFromAddress(string address)
{
//Your logic here
}
You can access these methods individually by using the .WithParameters like:
Dictionary<string, string> parameters = new Dictionary<string, string>();
parameters.Add("name", name);
var query = Table.WithParameters(parameters);
var results = await query.ToEnumerableAsync();
To access the address method
Dictionary<string, string> parameters = new Dictionary<string, string>();
parameters.Add("address", personsAddress);
var query = Table.WithParameters(parameters);
var results = await query.ToEnumerableAsync();
So the important part is:
As far as I'm aware you can only send strings. But I might be
wrong
The name in the parameters dictionary has to be the exact
name of the variable in your controller!
Use the following for a lookup by Id:
var table = client.GetTable<T>();
var record = await table.LookupAsync(id);
It uses the different endpoint - https://site.azurewebsites.net/tables/Table/{id}.
For more info, check the book: https://adrianhall.github.io/develop-mobile-apps-with-csharp-and-azure/chapter3/client/

Resources