Error VS403357 when batch creating many work items in Azure DevOps using .NET API - azure-devops

I'm trying to use the Azure DevOps .NET API to batch create WorkItems in a AzureDevOps repository, but when I submit the batch request, I'm getting back an error message: "VS403357: Work items in the batch are expected to be unique, but found work item with ID -1 in more than one request."
Here's my code:
public void ExecuteWorkItemMigration(int[] workItemIds, IProgress<ProgressResult> progress = null)
{
var wiql = "SELECT * FROM WorkItems";
var query = new Query(_workItemStore, wiql, workItemIds);
var workItemCollection = query.RunQuery();
string projectName = MainSettings.AzureDevOpsSettings.ProjectName;
List<WitBatchRequest> batchRequests = new List<WitBatchRequest>();
foreach (WorkItemTfs tfsWorkItem in workItemCollection)
{
JsonPatchDocument document = CreateJsonPatchDocument(tfsWorkItem);
string workItemType = GetWorkItemType(tfsWorkItem);
WitBatchRequest wibr = _azureDevopsWorkItemTrackingClient.CreateWorkItemBatchRequest(projectName, workItemType,
document, true, true);
batchRequests.Add(wibr);
}
List<WitBatchResponse> results = _azureDevopsWorkItemTrackingClient.ExecuteBatchRequest(batchRequests).Result;
}
private static JsonPatchDocument CreateJsonPatchDocument(WorkItemTfs tfsWorkItem, int id = -1)
{
var document = new JsonPatchDocument();
document.Add(
new JsonPatchOperation
{
Path = "/id",
Operation = Operation.Add,
Value = id
});
document.Add(
new JsonPatchOperation
{
Path = "/fields/System.Title",
Operation = Operation.Add,
Value = tfsWorkItem.Title
});
if (tfsWorkItem.Fields.Contains("ReproSteps"))
document.Add(
new JsonPatchOperation
{
Path = "/fields/Microsoft.VSTS.TCM.ReproSteps",
Operation = Operation.Add,
Value = tfsWorkItem.Fields["ReproSteps"].Value
});
}
Any suggestions about what I need to do to get this working properly?
I have tried submitting different unique ID's but it doesn't seem to prevent the error from happening.

You need to use unique negative ID's for creating the WorkItem ID.
Something like this:
public void ExecuteWorkItemMigration(int[] workItemIds, IProgress<ProgressResult> progress = null)
{
var wiql = "SELECT * FROM WorkItems";
var query = new Query(_workItemStore, wiql, workItemIds);
var workItemCollection = query.RunQuery();
string projectName = MainSettings.AzureDevOpsSettings.ProjectName;
List<WitBatchRequest> batchRequests = new List<WitBatchRequest>();
int id = -1;
foreach (WorkItemTfs tfsWorkItem in workItemCollection)
{
JsonPatchDocument document = CreateJsonPatchDocument(tfsWorkItem, id--);
string workItemType = GetWorkItemType(tfsWorkItem);
WitBatchRequest wibr = _azureDevopsWorkItemTrackingClient.CreateWorkItemBatchRequest(projectName, workItemType,
document, true, true);
batchRequests.Add(wibr);
}
List<WitBatchResponse> results = _azureDevopsWorkItemTrackingClient.ExecuteBatchRequest(batchRequests).Result;
}

Related

Azure Search CreateIndexAsync fails with CamelCase field names FieldBuilder

Azure Search V11
I can't get this to work. But with the standard FieldBuilder the index is created.
private static async Task CreateIndexAsync(SearchIndexClient indexClient, string indexName, Type type)
{
var builder = new FieldBuilder
{
Serializer = new JsonObjectSerializer(new JsonSerializerOptions {PropertyNamingPolicy = new CamelCaseNamingPolicy()})
};
var searchFields = builder.Build(type).ToArray();
var definition = new SearchIndex(indexName, searchFields);
await indexClient.CreateIndexAsync(definition);
}
`
public class CamelCaseNamingPolicy : JsonNamingPolicy
{
public override string ConvertName(string name)
{
return char.ToLower(name[0]) + name.Substring(1);
}
}
See our sample for FieldBuilder. Basically, you must use a naming policy for both FieldBuilder and the SearchClient:
var clientOptions = new SearchClientOptions
{
Serializer = new JsonObjectSerializer(
new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
}),
};
var builder = new FieldBuilder
{
Serializer = clientOptions.Serializer,
};
var index = new SearchIndex("name")
{
Fields = builder.Build(type),
};
var indexClient = new SearchIndexClient(uri, clientOptions);
await indexClient.CreateIndexAsync(index);
await Task.DelayAsync(5000); // can take a little while
var searchClient = new SearchClient(uri, clientOptions);
var response = await searchClient.SearchAsync("whatever");
While this sample works (our sample code comes from oft-executed tests), if you have further troubles, please be sure to post the exact exception message you are getting.

How to create/update work item with parent-child relation in C# using Azure Devops API in Azure Devops

I am trying to create/update work item in Azure devops using API. I am able to create/update with item if it does not have any relation. But if I specify relation e.g. parent-child then I am getting below error:
TF401349: An unexpected error has occurred, please verify your request and try again.
I am using JsonPatchDocument to create/update work item. Example below:
class Example
{
JsonPatchOperation AddRelationship(JsonPatchDocument doc, string rel, WorkItem linkedItem, bool isNew, int index)
{
//update link
if (!isNew)
{
return new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/relations/" + index,
Value = new { rel, url = linkedItem.Url, attributes = new { comment = "comment while update" } }
};
}
else
return new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/relations/-",
Value = new { rel, url = linkedItem.Url, attributes = new { comment = "Comment while creating item" } }
};
}
void Save()
{
// some code
doc.Add(AddRelationship(doc, "System.LinkTypes.Hierarchy-Forward", item, isNew, index++));
var workItem = isNew
? witClient.CreateWorkItemAsync(doc, Vsts.Project, issueType, bypassRules: true, validateOnly: Mode == ProcessingMode.ReadOnly).Result
: witClient.UpdateWorkItemAsync(doc, existingWorkItemId.Value, bypassRules: true, validateOnly: Mode == ProcessingMode.ReadOnly).Result;
}
}
}
Thank you.
I can not see definition for "rel" in your example. Something like this:
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/relations/-",
Value = new {
rel = "System.LinkTypes.Hierarchy-Forward",
url = RelUrl,
attributes = new
{
comment = "Comment for the link"
}
}
});
Maybe your code has to be like this:
JsonPatchOperation AddRelationship(JsonPatchDocument doc, string relname, WorkItem linkedItem, bool isNew, int index)
{
//update link
if (!isNew)
{
return new JsonPatchOperation()
{
Operation = Operation.Replace,
Path = "/relations/" + index + "/attributes/comment",
Value = "comment while update"
};
}
else
return new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/relations/-",
Value = new { rel = relname, url = linkedItem.Url, attributes = new { comment = "Comment while creating item" } }
};
}

CosmosDB Paging Return Value

I am trying to return paging results the request from CosmosDB. I saw this example from here but I am not sure what to do with the response variable.
// Fetch query results 10 at a time.
var queryable = client.CreateDocumentQuery<Book>(collectionLink, new FeedOptions { MaxItemCount = 10 });
while (queryable.HasResults)
{
FeedResponse<Book> response = await queryable.ExecuteNext<Book>();
}
Am I suppose to return it directly? Or do I have to do something further with the response variable? I tried to return the response variable directly and it's not working. Here's my code:
public async Task<IEnumerable<T>> RunQueryAsync(string queryString)
{
var feedOptions = new FeedOptions { MaxItemCount = 3 };
IQueryable<T> filter = _client.CreateDocumentQuery<T>(_collectionUri, queryString, feedOptions);
IDocumentQuery<T> query = filter.AsDocumentQuery();
var response = new FeedResponse<T>();
while (query.HasMoreResults)
{
response = await query.ExecuteNextAsync<T>();
}
return response;
}
Update:
After reading #Evandro Paula's answer, I followed the URL and changed my implementation to below. But it is still giving me 500 status code:
public async Task<IEnumerable<T>> RunQueryAsync(string queryString)
{
var feedOptions = new FeedOptions { MaxItemCount = 1 };
IQueryable<T> filter = _client.CreateDocumentQuery<T>(_collectionUri, queryString, feedOptions);
IDocumentQuery<T> query = filter.AsDocumentQuery();
List<T> results = new List<T>();
while (query.HasMoreResults)
{
foreach (T t in await query.ExecuteNextAsync())
{
results.Add(t);
}
}
return results;
}
And here's the exception message:
Cross partition query is required but disabled. Please set
x-ms-documentdb-query-enablecrosspartition to true, specify
x-ms-documentdb-partitionkey, or revise your query to avoid this
exception., Windows/10.0.17134 documentdb-netcore-sdk/1.9.1
Update 2:
I added the EnableCrossPartitionQuery to true and I am able to get the response from CosmosDB. But I am not able to get the 1 item that I defined. Instead, I got 11 items.
Find below a simple example on how to use the CosmosDB/SQL paged query:
private static async Task Query()
{
Uri uri = new Uri("https://{CosmosDB/SQL Account Name}.documents.azure.com:443/");
DocumentClient documentClient = new DocumentClient(uri, "{CosmosDB/SQL Account Key}");
int currentPageNumber = 1;
int documentNumber = 1;
IDocumentQuery<Book> query = documentClient.CreateDocumentQuery<Book>("dbs/{CosmoDB/SQL Database Name}/colls/{CosmoDB/SQL Collection Name}", new FeedOptions { MaxItemCount = 10 }).AsDocumentQuery();
while (query.HasMoreResults)
{
Console.WriteLine($"----- PAGE {currentPageNumber} -----");
foreach (Book book in await query.ExecuteNextAsync())
{
Console.WriteLine($"[{documentNumber}] {book.Id}");
documentNumber++;
}
currentPageNumber++;
}
}
Per exception described in your question Cross partition query is required but disabled, update the feed options as follows:
var feedOptions = new FeedOptions { MaxItemCount = 1, EnableCrossPartitionQuery = true};
Find a more comprehensive example at https://github.com/Azure/azure-documentdb-dotnet/blob/d17c0ca5be739a359d105cf4112443f65ca2cb72/samples/code-samples/Queries/Program.cs#L554-L576.
you are not specifying any where criteria for your specific item...so you are getting all results..try specifying criteria for the item (id , name etc) you are looking for. And keep in mind cross partition queries consume much more RUs n time, you can revisit architecture of your data model..Ideally always do queries with in same partition

When using NumericField, get absolutely nothing back every time

Been playing with Lucene.NET the last two days.
After reading up on Dates, I was led to believe that Dates are best converted to Milliseconds, and stored in NumericField, with Indexing=true, and Store=No.
But now nothing ever returns - it must be something basic, but I'm just not seeing it.
The saving code is as follows:
...
else if (type == typeof (DateTime?))
{
var typedValue = (DateTime?) value;
field = numericField = new NumericField(documentFieldName, 4, Field.Store.YES, true);
long milliseconds = typedValue.HasValue?(typedValue.Value.Date.Ticks/TimeSpan.TicksPerMillisecond):0;
numericField.SetLongValue(milliseconds);
doc.Add(numericField);
}
...
else
{
field = stringField = new Field(
documentFieldName,
(value != null)?value.ToString():string.Empty,
Store.YES,
Field.Index.ANALYZED) ;
doc.Add(stringField);
}
// Write the Document to the catalog
indexWriter.AddDocument(doc);
When I query for docs against the values saved in Field ... no problem.
When I query for documents by matching against the values in NumericFields, nothing returns.
Where did I go wrong?
Thanks for your help.
Lucene.Net.Analysis.Analyzer analyzer = new Lucene.Net.Analysis.Standard.StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30);
var q2 = NumericRangeQuery.NewLongRange("Val", 3, 3, true, true);
var uxy2 = documentSearchManagementService.Search("Students", termQuery, "Id");
Using:
public ScoredDocumentResult[] Search(string indexName, Query query, params string[] hitFieldNamesToReturn)
{
if (_configuration.IndexRootDirectory.IsNullOrEmpty())
{
throw new Exception("Configuration.IndexRootDirectory has not been configued yet.");
}
indexName.ValidateIsNotNullOrEmpty("indexName");
hitFieldNamesToReturn.ValidateIsNotDefault("hitFieldNamesToReturn");
//Specify the index file location where the indexes are to be stored
string indexFileLocation = Path.Combine(_configuration.IndexRootDirectory, indexName);
Lucene.Net.Store.Directory luceneDirectory = Lucene.Net.Store.FSDirectory.Open(indexFileLocation);
IndexSearcher indexSearcher = new IndexSearcher(luceneDirectory);
TopScoreDocCollector topScoreDocCollector = TopScoreDocCollector.Create(10, true);
indexSearcher.Search(query, topScoreDocCollector);
List<ScoredDocumentResult> results = new List<ScoredDocumentResult>();
foreach (var scoreDoc in topScoreDocCollector.TopDocs(0, 10).ScoreDocs)
{
ScoredDocumentResult resultItem = new ScoredDocumentResult();
Lucene.Net.Documents.Document doc = indexSearcher.Doc(scoreDoc.Doc);
resultItem.Score = scoreDoc.Score;
List<ScoredDocumentFieldResult> fields = new List<ScoredDocumentFieldResult>();
foreach (string fieldName in hitFieldNamesToReturn)
{
string fieldValue = doc.Get(fieldName);
fields.Add(new ScoredDocumentFieldResult{Key= fieldName,Value=fieldValue});
}
resultItem.FieldValues = fields.ToArray();
results.Add(resultItem);
}
indexSearcher.Close();
return results.ToArray();
}

Tridion CoreService create component missing metadata

i got exception = {"Unable to find uuid:5708986b-390f-4728-b0c7-b49bd3d8f407:Metadata."}
schemaId = UpdatePubId(schemaId, containerId);
SchemaData schemaData = (SchemaData)client.Read(schemaId, null);
string xml = string.Format("<{0} xmlns=\"{1}\">{2}</{0}>", schemaData.RootElementName, schemaData.NamespaceUri, fields);
ComponentData componentData = new ComponentData
{
Content = xml,
ComponentType = ComponentType.Normal,
Title = title,
Schema = new LinkToSchemaData { IdRef = schemaId },
LocationInfo = new LocationInfo { OrganizationalItem = new LinkToOrganizationalItemData { IdRef = containerId } },
Id = "tcm:0-0-0",
MetadataSchema = schemaData.MetadataSchema,
Metadata = schemaData.Metadata
};
try
{
componentData = client.Save(componentData, new ReadOptions()) as ComponentData;
componentData = client.CheckIn(componentData.Id, new ReadOptions()) as ComponentData;
message.Set("Component", title + ", successfully");
}
catch (Exception exception)
{
message.Set("Component", exception.Message);
}
thanks Tridion experts
You are on the correct path, but the error indicates that you have not provided the Metadata fields for the component that you are trying to create.
This line is incorrect:
Metadata = schemaData.Metadata
It should pretty much, like that one where you create the content fields:
Metadata = String.Format("<Metadata xmlns=\"{0}\">{1}</Metadata>",schemaData.NamespaceUri, "YOUR METADATA XML")