Azure Cosmos / DocumentDB issue when setting custom id property - azure

I want to insert documents to documentDb. This is working fine when I'm not adding a custom id property before inserting the doc.
Example
foreach (dynamic doc in docs)
{
if (doc != null)
{
doc.id = Convert.ToString(doc.myCustomId); // myCustomId is an integer
var addedDoc = await dbClient.UpsertDocumentAsync(collectionUri, doc);
addedDocuments.Add(addedDoc);
}
}
When I remove doc.id => the document is added with a guid as id.
The error is the following: "Can not convert Array to String."
" bei Newtonsoft.Json.Linq.JToken.op_Explicit(JToken value)\r\n bei Newtonsoft.Json.Linq.JToken.ToObject(Type objectType)\r\n bei Newtonsoft.Json.Linq.JToken.ToObject[T]()\r\n bei Microsoft.Azure.Documents.JsonSerializable.GetValue[T](String propertyName)\r\n bei Microsoft.Azure.Documents.Resource.get_Id()\r\n bei Microsoft.Azure.Documents.Client.DocumentClient.ValidateResource(Resource resource)\r\n
The code is executed in a Azure Function

This error can occur if you use a version of Newtonsoft.Json higher than 9.0.1. Try moving everything back to that version and see if it works. I'd also recommend using the DocumentDB package 1.13.2 to be safe.
This happens because the host is compiled against a specific version of these packages and if you try to use a newer version you can get subtle breaks like this. See this issue for details on how we're looking to address this: https://github.com/Azure/azure-webjobs-sdk-script/issues/992.

Related

ReadDocumentAsync always fails claiming Id does not exist

I am querying Azure DocumentDb (cosmos) for a document that is present in the container:
try{
doc = await client.ReadDocumentAsync(
GetDocumentUri("tenantString-campaignId"),
new RequestOptions
{
PartitionKey = new PartitionKey(tenantString)
});
}
catch(Exception e)
{
Console.WriteLine(e);
}
for this document:
tenantString-campaignId is the id you can see here and tenantString alone is the partition key this is under. The tenant itself was passed in as a string and I had that working but now I have changed to passing a Tenant object and parsing the string required from it I am not returning the document.
I have tried a few different variations of tenantString and id and I can generate either a DocumentClientException, Id does not exist, Exception or it fails silently; no exception and returns to the calling method where it causes a NullReferenceException as no document is returned.
As far as I can make out from debugging through this I am constructing all my data correctly and yet no document is returned. Does anyone have any idea what I can try next?
This syntax for the .NET SDK v2 is not correct. ReadDocumentAsync() should look like this.
var response = await client.ReadDocumentAsync(
UriFactory.CreateDocumentUri(databaseName, collectionName, "SalesOrder1"),
new RequestOptions { PartitionKey = new PartitionKey("Account1") });
You can see more v2 samples here

Get the Scan Operator when releasing documents

When releasing documents the scan operator should get logged to a file. I know this is a kofax system variable but how do I get it from the ReleaseData object?
Maybe this value is hold by the Values collection? What is the key then? I would try to access it by using
string scanOperator = documentData.Values["?scanOperator?"].Value;
Kofax's weird naming convention strikes again - during setup, said items are referred to as BatchVariableNames. However, during release they are KFX_REL_VARIABLEs (an enum named KfxLinkSourceType).
Here's how you can add all available items during setup:
foreach (var item in setupData.BatchVariableNames)
{
setupData.Links.Add(item, KfxLinkSourceType.KFX_REL_VARIABLE, item);
}
The following sample iterates over the DocumentData.Values collection, storing each BatchVariable in a Dictionary<string, string> named BatchVariables.
foreach (Value v in DocumentData.Values)
{
switch (v.SourceType)
{
case KfxLinkSourceType.KFX_REL_VARIABLE:
BatchVariables.Add(v.SourceName, v.Value);
break;
}
}
You can then access any of those variables by key - for example Scan Operator's User ID yields the scan user's domain and name.

npgsql to PostGIS adds ::text, making st_intersects fail

I've seen numerous old posts about this but no clear solution.
We use PostGreSQL 9.3 with PostGIS 2; NHibernate 3.2.0-GA with Npgsql 2.1.2.
We have an ASP.NET website witch uses MySQL Spatial and we are now in the progress of switching to PostGIS.
My query that fails is send to NHibernate using this code:
string hql = string.Format("select item from {0} item
where NHSP.Intersects(item.Polygon,:boundary)
and item.Layer = :layer", typeof(Data.Item).Name);
IQuery query = CurrentSession.CreateQuery(hql);
query.SetParameter("boundary", boundary, GeometryType);
query.SetParameter("layer", layer);
return query.List<Data.Item>();
This should generate a query like this:
select * from fields
where layer = 'tst'
and st_intersects(polygon,
'0103000020000000000100000005000000F[..]4A40');
But it generates a query like this:
select * from fields
where layer = 'tst'
and st_intersects(polygon,
'0103000020000000000100000005000000F[..]4A40'::text);
Notice the ::text at the end. This results in the following exception:
Npgsql.NpgsqlException: ERROR: 42725: function st_intersects(geometry, text) is not unique
The reason is because the second argument is send as text to PostGIS instead of a geometry.
I've change some code in the NH Spatial library, as suggested elsewhere:
I added these lines to GeometryTypeBase.cs (NHibernate.Spatial)
protected GeometryTypeBase(NullableType nullableType, SqlType sqlTypeOverwrite)
: this(nullableType)
{
this.sqlType = sqlTypeOverwrite;
}
And changed
public PostGisGeometryType()
: base(NHibernateUtil.StringClob)
{
}
into
public PostGisGeometryType()
: base(NHibernateUtil.StringClob, new NHibernate.SqlTypes.SqlType(System.Data.DbType.Object))
{
}
in PostGisGeometryType.cs (PostGIS driver)
When I run my application I now get a cast exception on
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
this.nullableType.NullSafeSet(cmd, this.FromGeometry(value), index);
}
also in GeometryTypeBase.cs (NHibernate.Spatial):
System.InvalidCastException: Can't cast System.String into any valid DbType.
Any suggestion how to fix this is much appreciated.
I've kept on searching and altering my search string I've found the answer in https://github.com/npgsql/Npgsql/issues/201
In NpgsqlTypes.NpgsqlTypesHelper.cs
nativeTypeMapping.AddType("text_nonbinary", NpgsqlDbType.Text, DbType.Object, true);
nativeTypeMapping.AddDbTypeAlias("text_nonbinary", DbType.Object);
needs to be changed to
nativeTypeMapping.AddType("unknown", NpgsqlDbType.Text, DbType.Object, true);
nativeTypeMapping.AddDbTypeAlias("unknown", DbType.Object);
And also my earlier fix to PostGisGeometryType needs to be done.
Now I finally can get my geometry data from PostGIS.

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

How do I perform a MOSS FullTextSqlQuery and filter people results by the Skills managed property?

I am having trouble with a MOSS FulltextSqlQuery when attempting to filter People results on the Skills Managed Property using the CONTAINS predicate. Let me demonstrate:
A query with no filters returns the expected result:
SELECT AccountName, Skills
from scope()
where freetext(defaultproperties,'+Bob')
And ("scope" = 'People')
Result
Total Rows: 1
ACCOUNTNAME: MYDOMAIN\Bob
SKILLS: Numchucks | ASP.Net | Application Architecture
But when I append a CONTAINS predicate, I no longer get the expected result:
SELECT AccountName, Skills
from scope()
where freetext(defaultproperties,'+Bob')
And ("scope" = 'People')
And (CONTAINS(Skills, 'Numchucks'))
Result
Total Rows: 0
I do realize I can accomplish this using the SOME ARRAY predicate, but I would like to know why this is not working with the CONTAINS predicate for the Skills property. I have been successful using the CONTAINS predicate with a custom crawled property that is indicated as 'Multi-valued'. The Skills property (though it seems to be multi-valued) is not indicated as such on the Crawled Properties page in the SSP admin site:
http:///ssp/admin/_layouts/schema.aspx?ConsoleView=crawledPropertiesView&category=People
Anyone have any ideas?
So with the help of Mark Cameron (Microsoft SharePoint Developer Support), I figured out that certain managed properties have to be enabled for full text search using the ManagedProperty object model API by setting the FullTextQueriable property to true. Below is the method that solved this issue for me. It could be included in a Console app or as a Farm or Web Application scoped Feature Receiver.
using Microsoft.Office.Server;
using Microsoft.Office.Server.Search.Administration;
private void EnsureFullTextQueriableManagedProperties(ServerContext serverContext)
{
var schema = new Schema(SearchContext.GetContext(serverContext));
var managedProperties = new[] { "SKILLS", "INTERESTS" };
foreach (ManagedProperty managedProperty in schema.AllManagedProperties)
{
if (!managedProperties.Contains(managedProperty.Name.ToUpper()))
continue;
if (managedProperty.FullTextQueriable)
continue;
try
{
managedProperty.FullTextQueriable = true;
managedProperty.Update();
Log.Info(m => m("Successfully set managed property {0} to be FullTextQueriable", managedProperty.Name));
}
catch (Exception e)
{
Log.Error(m => m("Error updating managed property {0}", managedProperty.Name), e);
}
}
}
SELECT AccountName, Skills
from scope()
where freetext(defaultproperties,'+Bob')
And ("scope" = 'People')
And (CONTAINS(Skills, 'Numchucks*'))
use the * in the end.
You also have a few more options to try:
The following list identifies
additional query elements that are
supported only with SQL search syntax
using the FullTextSqlQuery class:
FREETEXT()
CONTAINS()
LIKE
Source

Resources