Dynamics CRM - Accessing Custom Product Option Value - dynamics-crm-2011

Is there a way to programmatically access the Label & Value fields that has been created as a custom Field in MS CRM Dynamics please?
I have added a custom field called "new_producttypesubcode" which, for example, has 2 options, Trophy = 1000000 and Kit = 10000001.
I am writing an import utility that mirrors products between the customers website and their CRM and I want to get a list of all possible product options in the CRM to see if they are matched in the website.
So, in essence I want to...
get the list of possible new_producttypesubcodes and their corresponding values.
Iterate through the product variants in the website.
if the product variant name matches any name in the list of new_producttypecodes then add the value 1000000
So, if I find a product added to the website and its marked as a "Trophy" and "Trophy" exists in the CRM then new OptionSetValue(100000001)
I hope that makes sense...
Thanks

This function retrieves a dictionary of possible values localised to the current user. Taken from: CRM 2011 Programatically Finding the Values of Picklists, Optionsets, Statecode, Statuscode and Boolean (Two Options).
static Dictionary<String, int> GetNumericValues(IOrganizationService service, String entity, String attribute)
{
RetrieveAttributeRequest request = new RetrieveAttributeRequest
{
EntityLogicalName = entity,
LogicalName = attribute,
RetrieveAsIfPublished = true
};
RetrieveAttributeResponse response = (RetrieveAttributeResponse)service.Execute(request);
switch (response.AttributeMetadata.AttributeType)
{
case AttributeTypeCode.Picklist:
case AttributeTypeCode.State:
case AttributeTypeCode.Status:
return ((EnumAttributeMetadata)response.AttributeMetadata).OptionSet.Options
.ToDictionary(key => key.Label.UserLocalizedLabel.Label, option => option.Value.Value);
case AttributeTypeCode.Boolean:
Dictionary<String, int> values = new Dictionary<String, int>();
BooleanOptionSetMetadata metaData = ((BooleanAttributeMetadata)response.AttributeMetadata).OptionSet;
values[metaData.TrueOption.Label.UserLocalizedLabel.Label] = metaData.TrueOption.Value.Value;
values[metaData.FalseOption.Label.UserLocalizedLabel.Label] = metaData.FalseOption.Value.Value;
return values;
default:
throw new ArgumentOutOfRangeException();
}
}
So you would then need to do something like:
Dictionary<String, int> values = GetNumericValues(proxy, "your_entity", "new_producttypesubcode");
if(values.ContainsKey("Trophy"))
{
//Do something with the value
OptionSetValue optionSetValue = values["Trophy"];
int value = optionSetValue.Value;
}

Yes, that data is all stored in the metadata for an attribute (SDK article). You have to retrieve the entity metadata for the entity and then find the attribute in the list. Then cast that attribute to a PicklistAttributeMetadata object and it will contain a list of options. I would mention that typically retrieving Metadata from CRM is an expensive operation, so think about caching.
private static OptionSetMetadata RetrieveOptionSet(IOrganizationService orgService,
string entityName, string attributeName)
{
var entityResponse = (RetrieveEntityResponse)orgService.Execute(
new RetrieveEntityRequest
{ LogicalName = entityName, EntityFilters = EntityFilters.Attributes });
var entityMetadata = entityResponse.EntityMetadata;
for (int i = 0; i < entityMetadata.Attributes.Length; i++)
{
if (attributeName.Equals(entityMetadata.Attributes[i].LogicalName))
{
if (entityMetadata.Attributes[i].AttributeType.Value ==
AttributeTypeCode.Picklist)
{
var attributeMD = (PicklistAttributeMetadata)
entityMetadata.Attributes[i];
return attributeMD.OptionSet;
}
}
}
return null;
}
Here is how to write the options to the console using the above call.
var optionSetMD = RetrieveOptionSet(orgService, "account", "accountcategorycode");
var options = optionSetMD.Options;
for (int i = 0; i < options.Count; i++)
{
Console.WriteLine("Local Label: {0}. Value: {1}",
options[i].Label.UserLocalizedLabel.Label,
options[i].Value.HasValue ? options[i].Value.Value.ToString() : "null");
}
I believe this works for global option set attributes as well, but if you know it is a global option set there is a different message for it that would probably a bit more efficient (SDK article).

Related

QueryExpression with no results in Dynamics CRM plugin

I wrote the following function to get the SharePointDocumentLocation records regarding an account or contact. However, even though I provide an id which most definitely has got a SPDL record associated the result of a count on the EntityCollection that is returned is alway 0. Why does my query not return SPDL records?
internal static EntityCollection GetSPDocumentLocation(IOrganizationService service, Guid id)
{
SharePointDocumentLocation spd = new SharePointDocumentLocation();
QueryExpression query = new QueryExpression
{
EntityName = "sharepointdocumentlocation",
ColumnSet = new ColumnSet("sharepointdocumentlocationid"),
Criteria = new FilterExpression
{
Conditions =
{
new ConditionExpression
{
AttributeName = "regardingobjectid",
Operator = ConditionOperator.Equal,
Values = { id }
}
}
}
};
return service.RetrieveMultiple(query);
}
The following code does work
using System;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using System.ServiceModel.Description;
using System.Net;
using Microsoft.Xrm.Sdk.Query;
namespace CRMConsoleTests
{
class Program
{
static void Main(string[] args)
{
ClientCredentials credentials = new ClientCredentials();
credentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials;
Uri orgUri = new Uri("http://localhost/CRMDEV2/XRMServices/2011/Organization.svc");
Uri homeRealmUri = null;
using (OrganizationServiceProxy service = new OrganizationServiceProxy(orgUri, homeRealmUri, credentials, null))
{
//ConditionExpression ce = new ConditionExpression("regardingobjectid", ConditionOperator.Equal, new Guid(""));
QueryExpression qe = new QueryExpression("sharepointdocumentlocation");
qe.ColumnSet = new ColumnSet(new String[] { "sharepointdocumentlocationid", "regardingobjectid" });
//qe.Criteria.AddCondition(ce);
EntityCollection result = service.RetrieveMultiple(qe);
foreach (Entity entity in result.Entities)
{
Console.WriteLine("Results for the first record: ");
SharePointDocumentLocation spd = entity.ToEntity<SharePointDocumentLocation>();
if (spd.RegardingObjectId != null)
{
Console.WriteLine("Id: " + spd.SharePointDocumentLocationId.ToString() + " with RoId: " + spd.RegardingObjectId.Id.ToString());
}
}
Console.ReadLine();
}
}
}
}
It retrieves 4 records, and when I debug the plugincode above it retrieves 3 records.
Everything looks good with your QueryExpression, although I'd write it a little more concise (something like this):
var qe = new QueryExpression(SharePointDocumentLocation.EntityLogicalName){
ColmnSet = new ColumnSet("sharepointdocumentlocationid"),
};
qe.Criteria.AddCondition("regardingobjectid", ConditionOperator.Equal, id);
Because I don't see anything wrong with the QueryExpression that leads me with two guesses.
You're using impersonation on the IOrganizationService and the impersonated user doesn't have rights to the SharePointDocumentLocation. You won't get an error, you just won't get any records returned.
The id you're passing in is incorrect.
I'd remove the Criteria and see how many records you get back. If you don't get all of the records back, you know your issue is with guess #1.
If you get all records, add the regardingobjectid to the ColumnSet and retrieve the first record without any Criteria in the QueryExpression, then call this method passing in the id of the regardingobject you returned. If nothing is received when adding the regardingobjectid constraint, then something else is wrong.
Update
Since this is executing within the delete of the plugin, it must be performing its cascade deletes before your plugin is firing. You can try the Pre-Validation.
Now that I think of it, it must perform the deletion of the cascading entities in the Validation stage, because if one of them is unable to be deleted, the entity itself can't be deleted.

Creating a unattached Entity Framework DbContext entity

So I'm working on an app that will select data from one database and update an identical database based on information contained in a 'Publication' Table in the Authoring database. I need to get a single object that is not connected to the 'Authoring' context so I can add it to my 'Delivery' context.
Currently I am using
object authoringRecordVersion = PublishingFactory.AuthoringContext.Set(recordType.Entity.GetType()).Find(record.RecordId);
object deliveryRecordVersion = PublishingFactory.DeliveryContext.Set(recordType.Entity.GetType()).Find(record.RecordId));
to return my records. Then if the 'deliveryRecordVersion' is null, I need to do an Insert of 'authoringRecordVersion' into 'PublishingFactory.DeliveryContext'. However, that object is already connected to the 'PublishingFactory.AuthoringContext' so it won't allow the Add() method to be called on the 'PublishingFactory.DeliveryContext'.
I have access to PublishingFactory.AuthoringContext.Set(recordType.Entity.GetType()).AsNoTracking()
but there is no way to get at the specific record I need from here.
Any suggestions?
UPDATE:
I believe I found the solution. It didn't work the first time because I was referencing the wrong object on when setting .State = EntityState.Detached;
here is the full corrected method that works as expected
private void PushToDelivery(IEnumerable<Mkl.WebTeam.UWManual.Model.Publication> recordsToPublish)
{
string recordEntity = string.Empty;
DbEntityEntry recordType = null;
// Loop through recordsToPublish and see if the record exists in Delivery. If so then 'Update' the record
// else 'Add' the record.
foreach (var record in recordsToPublish)
{
if (recordEntity != record.Entity)
{
recordType = PublishingFactory.DeliveryContext.Entry(ObjectExt.GetEntityOfType(record.Entity));
}
if (recordType == null)
{
continue;
////throw new NullReferenceException(
//// string.Format("Couldn't identify the object type stored in record.Entity : {0}", record.Entity));
}
// get the record from the Authoring context from the appropriate type table
object authoringRecordVersion = PublishingFactory.AuthoringContext.Set(recordType.Entity.GetType()).Find(record.RecordId);
// get the record from the Delivery context from the appropriate type table
object deliveryRecordVersion = PublishingFactory.DeliveryContext.Set(recordType.Entity.GetType()).Find(record.RecordId);
// somthing happened and no records were found meeting the Id and Type from the Publication table in the
// authoring table
if (authoringRecordVersion == null)
{
continue;
}
if (deliveryRecordVersion != null)
{
// update record
PublishingFactory.DeliveryContext.Entry(deliveryRecordVersion).CurrentValues.SetValues(authoringRecordVersion);
PublishingFactory.DeliveryContext.Entry(deliveryRecordVersion).State = EntityState.Modified;
PublishingFactory.DeliveryContext.SaveChanges();
}
else
{
// insert new record
PublishingFactory.AuthoringContext.Entry(authoringRecordVersion).State = EntityState.Detached;
PublishingFactory.DeliveryContext.Entry(authoringRecordVersion).State = EntityState.Added;
PublishingFactory.DeliveryContext.SaveChanges();
}
recordEntity = record.Entity;
}
}
As you say in your comment the reason why you can't use .Single(a => a.ID == record.RecordId) is that the ID property is not known at design time. So what you can do is get the entity by the Find method and then detach it from the context:
PublishingFactory.AuthoringContext
.Entry(authoringRecordVersion).State = EntityState.Detached;

Check if a List Column Exists using SharePoint Client Object Model?

Using the Client Object Model (C#) in SharePoint 2010, how can I determine if a specified column (field) name exists in a given List?
Thanks, MagicAndi.
Just found this while searching for the same thing, but it looks like Sharepoint 2010 has something built in for this, at least for the Server model: list.Fields.ContainsField("fieldName");
Not sure if it exists for Client side though. Figured it would be a good place to store this information however.
Server Object Model
string siteUrl = "http://mysite";
using (SPSite site = new SPSite(siteUrl))
{
using (SPWeb web = site.OpenWeb())
{
SPList list = web.Lists["my forum"];
for (int i = 0; i < list.Fields.Count; i++)
{
if (list.Fields[i].Title == "xyz")
{
-
-
}
}
}
}
Client Object Model
string siteUrl = "http://MyServer/sites/MySiteCollection";
ClientContext clientContext = new ClientContext(siteUrl);
SP.List List = clientContext.Web.Lists.GetByTitle("my forum");
for (int i = 0; i < list.Fields.Count; i++)
{
if (list.Fields[i].Title == "xyz")
{
-
-
}
}
The following method demonstrates how to determine whether a specified column exists in a List using CSOM:
static class FieldCollectionExtensions
{
public static bool ContainsField(this List list,string fieldName)
{
var ctx = list.Context;
var result = ctx.LoadQuery(list.Fields.Where(f => f.InternalName == fieldName));
ctx.ExecuteQuery();
return result.Any();
}
}
Usage
using(var ctx = new ClientContext(webUrl))
{
var list = ctx.Web.Lists.GetByTitle(listTitle);
if(list.ContainsField("Title")){
//...
}
}
Here's an extension code (CSOM) for sharepoint list
public static bool DoesFieldExist(this List list, ClientContext clientContext, string internalFieldname)
{
bool exists = false;
clientContext.Load(list.Fields, fCol => fCol.Include(
f => f.InternalName
).Where(field => field.InternalName == internalFieldname));
clientContext.ExecuteQuery();
if (list.Fields != null && list.Fields.Count > 0)
{
exists = true;
}
return exists;
}
usage
List targetList = this.Context.Web.Lists.GetById(<ListID>);
targetList.DoesFieldExist(<ClientContext>, <Field internal Name>)
enjoy :)
I ended up retrieving the details of the list's fields prior to my operation, and saving them in a generic list of structs (containing details of each field). I then query this (generic) list to see if the current field actually exists in the given (SharePoint) list.
// Retrieve detail sof all fields in specified list
using (ClientContext clientContext = new ClientContext(SharePointSiteUrl))
{
List list = clientContext.Web.Lists.GetByTitle(listName);
_listFieldDetails = new List<SPFieldDetails>();
// get fields name and their types
ClientObjectPrototype allFields = list.Fields.RetrieveItems();
allFields.Retrieve( FieldPropertyNames.Title,
FieldPropertyNames.InternalName,
FieldPropertyNames.FieldTypeKind,
FieldPropertyNames.Id,
FieldPropertyNames.ReadOnlyField);
clientContext.ExecuteQuery();
foreach (Field field in list.Fields)
{
SPFieldDetails fieldDetails = new SPFieldDetails();
fieldDetails.Title = field.Title;
fieldDetails.InternalName = field.InternalName;
fieldDetails.Type = field.FieldTypeKind;
fieldDetails.ID = field.Id;
fieldDetails.ReadOnly = field.ReadOnlyField;
listFieldDetails.Add(fieldDetails);
}
}
// Check if field name exists
_listFieldDetails.Exists(field => field.Title == fieldName);
// Struct to hold details of the field
public struct SPFieldDetails
{
public string Title { get; set; }
public string InternalName { get; set; }
public Guid ID { get; set; }
public FieldType Type { get; set; }
public bool ReadOnly { get; set; }
}
Some good answers above. I personally used this one:
List list = ctx.Web.Lists.GetByTitle("Some list");
FieldCollection fields = list.Fields;
IEnumerable<Field> fieldsColl = ctx.LoadQuery(fields.Include(f => f.InternalName));
ctx.ExecuteQuery();
bool fieldMissing = fieldsColl.Any(f => f.InternalName != "Internal_Name");
You can also use 'Where' after Include method and check if returned collection/field is null. It's about personal preference, because both options are querying on client side.
I prefer the SharePoint Plus Library as it is really clean:
http://aymkdn.github.io/SharepointPlus/symbols/%24SP%28%29.list.html
$SP().list("My List").get({
fields:"Title",
where:"Author = '[Me]'"
},function getData(row) {
console.log(row[0].getAttribute("Title"));
});
You could setup a for loop to loop through the row and check if the column you're looking for exists.
A cut down and simplified version of Mitya's extension method:
public static bool FieldExists(this List list, string internalFieldname)
{
using (ClientContext clientContext = list.Context as ClientContext)
{
clientContext.Load(list.Fields, fCol => fCol.Include(
f => f.InternalName
).Where(field => field.InternalName == internalFieldname));
clientContext.ExecuteQuery();
return (list.Fields != null) && (list.Fields.Count > 0);
}
}
There's no need to pass in a separate client context parameter when you can already use the context that comes in with the list.
to much code use this
load Fields first then
bool exists= clientContext2.Site.RootWeb.Fields.Any(o => o.Id.ToString() == a.Id.ToString());

Anonymous type and getting values out side of method scope

I am building an asp.net site in .net framework 4.0, and I am stuck at the method that supposed to call a .cs class and get the query result back here is my method call and method
1: method call form aspx.cs page:
helper cls = new helper();
var query = cls.GetQuery(GroupID,emailCap);
2: Method in helper class:
public IQueryable<VariablesForIQueryble> GetQuery(int incomingGroupID, int incomingEmailCap)
{
var ctx = new some connection_Connection();
ObjectSet<Members1> members = ctx.Members11;
ObjectSet<groupMember> groupMembers = ctx.groupMembers;
var query = from m in members
join gm in groupMembers on m.MemberID equals gm.MemID
where (gm.groupID == incomingGroupID) && (m.EmailCap == incomingEmailCap)
select new VariablesForIQueryble(m.MemberID, m.MemberFirst, m.MemberLast, m.MemberEmail, m.ValidEmail, m.EmailCap);
//select new {m.MemberID, m.MemberFirst, m.MemberLast, m.MemberEmail, m.ValidEmail, m.EmailCap};
return query ;
}
I tried the above code with IEnumerable too without any luck. This is the code for class VariablesForIQueryble:
3:Class it self for taking anonymouse type and cast it to proper types:
public class VariablesForIQueryble
{
private int _emailCap;
public int EmailCap
{
get { return _emailCap; }
set { _emailCap = value; }
}`....................................
4: and a constructor:
public VariablesForIQueryble(int memberID, string memberFirst, string memberLast, string memberEmail, int? validEmail, int? emailCap)
{
this.EmailCap = (int) emailCap;
.........................
}
I can't seem to get the query result back, first it told me anonymous type problem, I made a class after reading this: link text; and now it tells me constructors with parameters not supported. Now I am an intermediate developer, is there an easy solution to this or do I have to take my query back to the .aspx.cs page.
If you want to project to a specific type .NET type like this you will need to force the query to actually happen using either .AsEnumerable() or .ToList() and then use .Select() against linq to objects.
You could leave your original anonymous type in to specify what you want back from the database, then call .ToList() on it and then .Select(...) to reproject.
You can also clean up your code somewhat by using an Entity Association between Groups and Members using a FK association in the database. Then the query becomes a much simpler:
var result = ctx.Members11.Include("Group").Where(m => m.Group.groupID == incomingGroupID && m.EmailCap == incomingEmailCap);
You still have the issue of having to do a select to specify which columns to return and then calling .ToList() to force execution before reprojecting to your new type.
Another alternative is to create a view in your database and import that as an Entity into the Entity Designer.
Used reflection to solve the problem:
A: Query, not using custom made "VariablesForIQueryble" class any more:
//Method in helper class
public IEnumerable GetQuery(int incomingGroupID, int incomingEmailCap)
{
var ctx = new some_Connection();
ObjectSet<Members1> members = ctx.Members11;
ObjectSet<groupMember> groupMembers = ctx.groupMembers;
var query = from m in members
join gm in groupMembers on m.MemberID equals gm.MemID
where ((gm.groupID == incomingGroupID) && (m.EmailCap == incomingEmailCap)) //select m;
select new { m.MemberID, m.MemberFirst, m.MemberLast, m.MemberEmail, m.ValidEmail, m.EmailCap };
//select new VariablesForIQueryble (m.MemberID, m.MemberFirst, m.MemberLast, m.MemberEmail, m.ValidEmail, m.EmailCap);
//List<object> lst = new List<object>();
//foreach (var i in query)
//{
// lst.Add(i.MemberEmail);
//}
//return lst;
//return query.Select(x => new{x.MemberEmail,x.MemberID,x.ValidEmail,x.MemberFirst,x.MemberLast}).ToList();
return query;
}
B:Code to catch objects and conversion of those objects using reflection
helper cls = new helper();
var query = cls.GetQuery(GroupID,emailCap);
if (query != null)
{
foreach (var objRow in query)
{
System.Type type = objRow.GetType();
int memberId = (int)type.GetProperty("MemberID").GetValue(objRow, null);
string memberEmail = (string)type.GetProperty("MemberEmail").GetValue(objRow, null);
}
else
{
something else....
}

Programmatically set a TaxonomyField on a list item

The situation:
I have a bunch of Terms in the Term Store and a list that uses them.
A lot of the terms have not been used yet, and are not available yet in the TaxonomyHiddenList.
If they are not there yet they don't have an ID, and I can not add them to a list item.
There is a method GetWSSIdOfTerm on Microsoft.SharePoint.Taxonomy.TaxonomyField that's supposed to return the ID of a term for a specific site.
This gives back IDs if the term has already been used and is present in the TaxonomyHiddenList, but if it's not then 0 is returned.
Is there any way to programmatically add terms to the TaxonomyHiddenList or force it happening?
Don't use
TaxonomyFieldValue tagValue = new TaxonomyFieldValue(termString);
myItem[tagsFieldName] = tagValue;"
because you will have errors when you want to crawl this item.
For setting value in a taxonomy field, you have just to use :
tagsField.SetFieldValue(myItem , myTerm);
myItem.Update();"
Regards
In case of usage
string termString = String.Concat(myTerm.GetDefaultLabel(1033),
TaxonomyField.TaxonomyGuidLabelDelimiter, myTerm.Id);
then during instantiation TaxonomyFieldValue
TaxonomyFieldValue tagValue = new TaxonomyFieldValue(termString);
exception will be thrown with message
Value does not fall within the expected range
You have additionally provide WssId to construct term string like shown below
// We don't know the WssId so default to -1
string termString = String.Concat("-1;#",myTerm.GetDefaultLabel(1033),
TaxonomyField.TaxonomyGuidLabelDelimiter, myTerm.Id);
On MSDN you can find how to create a Term and add it to TermSet. Sample is provided from TermSetItem class description. TermSet should have a method CreateTerm(name, lcid) inherited from TermSetItem. Therefore you can use it in the sample below int catch statement ie:
catch(...)
{
myTerm = termSet.CreateTerm(myTerm, 1030);
termStore.CommitAll();
}
As for assigning term to list, this code should work (i'm not sure about the name of the field "Tags", however it's easy to find out the proper internal name of the taxonomy field):
using (SPSite site = new SPSite("http://myUrl"))
{
using (SPWeb web = site.OpenWeb())
{
string tagsFieldName = "Tags";
string myListName = "MyList";
string myTermName = "myTerm";
SPListItem myItem = web.Lists[myListName].GetItemById(1);
TaxonomyField tagsField = (TaxonomyField) myList.Fields[tagsFieldName];
TaxonomySession session = new TaxonomySession(site);
TermStore termStore = session.TermStores[tagsField.SspId];
TermSet termSet = termStore.GetTermSet(tagsField.TermSetId);
Term myTerm = null;
try
{
myTerm = termSet.Terms[myTermName];
}
catch (ArgumentOutOfRangeException)
{
// ?
}
string termString = String.Concat(myTerm.GetDefaultLabel(1033),
TaxonomyField.TaxonomyGuidLabelDelimiter, myTerm.Id);
if (tagsField.AllowMultipleValues)
{
TaxonomyFieldValueCollection tagsValues = new TaxonomyFieldValueCollection(tagsField);
tagsValues.PopulateFromLabelGuidPairs(
String.Join(TaxonomyField.TaxonomyMultipleTermDelimiter.ToString(),
new[] { termString }));
myItem[tagsFieldName] = tagsValues;
}
else
{
TaxonomyFieldValue tagValue = new TaxonomyFieldValue(termString);
myItem[tagsFieldName] = tagValue;
}
myItem.Update();
}
}

Resources