Empty value in attribute | Plugin CRM C# - c#

I change only 1 field in the interface - "telephone1", and leave the second one unchanged, in the end I want field 3 to contain both 1 changed field and 2 field unchanged, but for some reason it is empty, although it contains values, it also works and vice versa. "InputParametres" as I understand it is a bad idea, but what other options are there?
https://i.imgur.com/7G3rRVK.png
if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
{
try
{
var createReq = new CreateRequest() { Parameters = context.InputParameters };
var res = createReq.Target; // Has type Entity
// Получить целевой объект из входных параметров
Entity entityInput = (Entity)context.InputParameters["Target"];
// entityInput.Attributes["telephone3"] = entityInput. + ";" + entityInput.Attributes["telephone2"];
entityInput.Attributes["telephone3"] = res.GetAttributeValue<string>("telephone1") + ";" + res.GetAttributeValue<string>("telephone2");
// service.Update(entityInput);
}
catch (Exception ex)
{
throw new Exception($"Error in update telephone3 - {ex.Message}");
}
}
else
{
throw new InvalidPluginExecutionException($"Плагин {nameof(Class1)} был зарегистрирован неправильно");
}

Target will have only changed attributes and it’s values. If you need the values from unchanged attributes (fields) you have to use PreImage to consume them.
You can register PreImage in Plug-in registration tool similar to steps, filtering attributes, etc.
Read more

Related

C# Modify Security Scopes on existing Task Sequence

I can modify all properties in SMS_TaskSequencePackage except SecuredScopeNames
public void setSecurityScopes(WqlConnectionManager connection, string packageID, string newSecurityScopes)
{
try
{
// Take the new security scopes (Security Scopes are stored in strings' array)
string[] newScopes = { newSecurityScopes };
// Get the instance with WQLConnectionManager
IResultObject securityScopesToChange = connection.GetInstance(#"SMS_TaskSequencePackage.PackageID='" + packageID + "'");
securityScopesToChange["SecuredScopeNames"].StringArrayValue = newScopes;
// Apply the new Security Scopes
securityScopesToChange.Put();
}
catch (SmsException ex)
{
MessageBox.Show("Failed to set TS Security Scopes. Error: " + ex.Message);
}
}
I don't understand why my StringArray isn't stored.
With this method, I can change others properties in string format but not this one.
Thanks to help me.
The property SecuredScopeNames seems to be a read only parameter (as can be seen in the documentation for SMS_Package - although it seems to be a bit outdated) that has to be modified via an additonal class SMS_SecuredCategoryMembership.
You can add a scope like this (see more details here):
// Create a new instance of the scope assignment.
IResultObject assignment = sccmConnection.CreateInstance("SMS_SecuredCategoryMembership");
// Configure the assignment
assignment.Properties["CategoryID"].StringValue = scopeId; // CategoryID from SMS_SecuredCategory
assignment.Properties["ObjectKey"].StringValue = objectKey; // PackageID
assignment.Properties["ObjectTypeID"].IntegerValue = objectTypeId; // can get it from SMS_ObjectName with objectkey (probably fixed values)
// Commit the assignment
assignment.Put();
The CategoryID or ScopeId can be taken from SMS_SecuredCategory
The ObjectKey is is the Package Id of your Package or TaskSequence Package
The ObjectTypeId is probably always 20 for TaskSequencePackages but can be queried from SMS_ObjectName with the PackageID as ObjectKey (very sloq query if done without where clause because it has all objects of all types that are stored in the sccm db)
This is however not enough for a real modification because it will keep all existing scopes so if you want to get rid of default (or another one) you will also have to call a remove (more detail here):
// Find the existing scope assignement that matches our parameters.
IResultObject assignment = sccmConnection.GetInstance("SMS_SecuredCategoryMembership.CategoryID='" + scopeId + "',ObjectKey='" + objectKey + "',ObjectTypeID=" + objectTypeId.ToString());
// Make sure we found the scope.
if(assignment == null)
throw new Exception("Unable to find matching scope, object, and object type.");
else
assignment.Delete();
With the same three parameters (Default seems to have the reserved scopeID SMS00UNA but it is probably still best to get the details from SMS_SecuredCategory).

'The given key was not present in the dictionary' - but the key exists

I am currently developing a MS Dynamics CRM 2013 - Plugin.
When I try to assign a string-value to a key of a field of an entity it gives me the 'keynotfound'-exception.
This leaves me clueless, because I can verify the key is existing. The key I give is also written correctly, and the data types are compatible, too.
Here's some extra info:
I tried resolving the issue with a server reboot. Nothing.
Remote Debugging is not an option.
I swapped "retrieved.EntityCollection.Entities[i][forField]" with retrieved.EntityCollection.Entities[i]["new_name"] and everything was working fine (kind of pointing out the obvious here, but "new_name" is not the key I try to access).
The execution stops # "if (retrieved.EntityCollection.Entities[i][forField].ToString() != "" && !overwriteExisting)"
Have you got an idea to help me out?
public void GenerateNumberForEntityCollection(string target)
{
try
{
// variables for number generation
bool overwriteExisting = (bool)preImageEntity["new_overwriteexisting"];
int suffixstart = (int)preImageEntity["new_suffixstart"];
string forField= preImageEntity["new_forfield"].ToString();
string prefix = preImageEntity["new_prefix"].ToString();
string postfix = preImageEntity["new_postfix"].ToString();
string separator = preImageEntity["new_separator"].ToString();
// Build query to get all the entries
RetrieveMultipleResponse retrieved;
int PageNumber = 1;
string PagingCookie = string.Empty;
int PageSize = 5000;
string[] Columns = { forField };
QueryExpression query = new QueryExpression()
{
EntityName = target,
ColumnSet = new ColumnSet(Columns),
PageInfo = new PagingInfo()
{
PageNumber = 1,
Count = PageSize
}
};
do
{
if (PageNumber != 1)
{
query.PageInfo.PageNumber = PageNumber;
query.PageInfo.PagingCookie = PagingCookie;
}
RetrieveMultipleRequest retrieve = new RetrieveMultipleRequest();
retrieve.Query = query;
retrieved = (RetrieveMultipleResponse)service.Execute(retrieve);
// Now that all entities are retrieved, iterate through them to gen. the numbers
int i = 0;
foreach (Entity entity in retrieved.EntityCollection.Entities)
{
if (retrieved.EntityCollection.Entities[i][forField].ToString() != "" && !overwriteExisting)
{
//continue;
}
else
{
retrieved.EntityCollection.Entities[i][forField] = prefix + separator + suffixstart.ToString() + separator + postfix;
}
suffixstart++;
service.Update(retrieved.EntityCollection.Entities[i]);
i++;
}
if (retrieved.EntityCollection.MoreRecords)
{
PageNumber++;
PagingCookie = retrieved.EntityCollection.PagingCookie;
}
} while (retrieved.EntityCollection.MoreRecords);
}
catch (Exception e)
{
tracing.Trace("GenerateNumberForEntityCollection: Failed: {0}", e.ToString());
}
}
How did you verify that the key exists?
If the data in a field is null, the Entity instance will not contain that key, even if you specify it in the query's ColumnSet.
This will return you a boolean, indicating if the key exists in the Entity. You can do this control before attempting to read the attribute.
var attributeExists = retrieved.EntityCollection.Entities[i].Contains(forField)
The control below you've done will result in the exception you're getting if the field is null. Just make sure that the attribute exists before.
retrieved.EntityCollection.Entities[i][forField].ToString() != ""
Additionally, you'll get a null reference exception if no records were returned from the query. Make you do a null check on retrieved.EntityCollection.Entities.
When you are querying data in Dynamics CRM it is important to know that record fields having null values in the database are not included in the Attributes collection of the Entity instances being returned.
Getting a value from an Entity's Attribute with this construct:
var value = retrieved.EntityCollection.Entities[i][forField].ToString();
succeeds when attribute forField already has a value in the database, but fails when its current value is null.
Therefore the preferred method to get the attribute values from an entity is GetAttributeValue<T>, like this:
var value = retrieved.EntityCollection.Entities[i].getAttributeValue<string>(forField);
This method returns the value when the attribute exists in the attribute collection, otherwise it returns null.
If any of the fields among
(new_forfield,new_prefix,new_postfix,new_separator) has null value,
that column does not present in the retrieved object and you are trying to get the value of null column preImageEntity["new_forfield"] which will throw keynotfound'-exception ,
so change the code
string forField= preImageEntity["new_forfield"].ToString();
string prefix = preImageEntity["new_prefix"].ToString();
string postfix = preImageEntity["new_postfix"].ToString();
string separator = preImageEntity["new_separator"].ToString();
to
string forField = preImageEntity.Attributes.Contains("new_forfield")? preImageEntity["new_forfield"].ToString():"";
string prefix = preImageEntity.Attributes.Contains("new_forfield") ? preImageEntity["new_prefix"].ToString() : "";
string postfix = preImageEntity.Attributes.Contains("new_forfield") ? preImageEntity["new_postfix"].ToString() : "";
string separator = preImageEntity.Attributes.Contains("new_forfield") ? preImageEntity["new_separator"].ToString() : "";
this will check for field, if it exists than will parse the value to
string else will assign empty string.

SSAS: Programmatically create attribute relationships

Using AMO I can happily confirm the existance of two dimension attributes. I'd like to create a relationship. The following (result) returns a result code of 0 what ever that means however after processing the cube there is no relation. Suggestions?
// confirm db exists
db = objServer.Databases.FindByName(strCubeDBName);
// find the dimension
string dimName = "Product";
Dimension dim = db.Dimensions.FindByName(dimName);
attrSource = dim.Attributes.FindByName("Spanish Product Subcategory Name");
if (attrSource != null)
Console.WriteLine(attrSource + " - source attribute exists");
else
throw new Exception(attrSource + " - source attribute does not exist");
attrRelated = dim.Attributes.FindByName("French Product Subcategory Name");
if (attrRelated != null)
Console.WriteLine(attrRelated + " - Related attribute exists");
else
throw new Exception(attrRelated + " - Related attribute does not exist");
int result;
result = attrSource.AttributeRelationships.Add(new AttributeRelationship(attrRelated));
Console.WriteLine(result);
dim.Process(ProcessType.ProcessUpdate);
After adding the new attribute relationship, you need to call Update on the dimension. In your code above, just before calling Process, add the following line:
dim.Update(UpdateOptions.ExpandFull | UpdateOptions.AlterDependents)

Null as value in HttpRuntime.Cache.Add

I want to store null for some of the keys in HttpRuntime.Cache as I dont want to again go to Database to find that there is no entry for that key.
So first time, it goes to database and fills the cache. The intent is to serve the following calls using cached data instead of doing the database call.
Here is the code that I am using the following:
Info info = null;
if (HttpRuntime.Cache["Info_" + id.ToString() + "_" + quantity.ToString()] != null)
info = HttpRuntime.Cache["Info_" + id.ToString() + "_" + quantity.ToString()] as Info;
if (info == null)
{
info = (from dd in dc.Infos
where dd.id == id && dd.active == true && dd.quantitytooffset == quantity
select dd).SingleOrDefault();
HttpRuntime.Cache.Add("Info_" + id.ToString() + "_" + quantity.ToString(), info, null, System.Web.Caching.Cache.NoAbsoluteExpiration, System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.High, null);
}
The last line of code i.e. HttpRuntime.Cache.Add throws an System.ArgumentNullException: Value cannot be null.
Any idea if this is possible or I need to use other datastructure to store null values and look up later?
You could use your own "null" value to place into cache.
For example,
private static Info NULL_INFO = new Info();
Then you could use that instead of null in HttpRuntime.Cache.Add and later after retrieval from cache check that you didn't get your NULL_INFO
if ( info == NULL_INFO) // if you don't have equality operator overloaded, otherwise you'd better use ReferenceEquals()
// return empty data
else if (info == null)
// proceed with try to load from database
I wrote a blog post recently about how the null keyword is often abused leading to this kind of confusion. In your particular case I would look at using an option type to indicate the lack or presence of data rather than null.
I have a simple implementation of an Option type you can use here
The usage would then be something like:
if (HttpRuntime.Cache["xyz"] == null)
// Impossible to make a distinction between whether or not the cache value is missing
// or if it is present but explicitly a null value...
HttpRuntime.Cache["xyz"] = Option<String>.None();
// We have now explicitly stated that the cache contains xyz but the value is null...
HttpRuntime.Cache["xyz"] = Option<String>.Some("hello world");
if (HttpRuntime.Cache["xyz"].IsSome)
{
// cache contains a non-null value for xyz...
}
You just need to check whether the value obtained from your data source is not null before trying to add it back to the cache, see below:
Info info = null;
if (HttpRuntime.Cache["Info_" + id.ToString() + "_" + quantity.ToString()] != null)
info = HttpRuntime.Cache["Info_" + id.ToString() + "_" + quantity.ToString()] as Info;
if (info == null)
{
info = (from dd in dc.Infos
where dd.id == id && dd.active == true && dd.quantitytooffset == quantity
select dd).SingleOrDefault();
if (info != null)
{
HttpRuntime.Cache.Add("Info_" + id.ToString() + "_" + quantity.ToString(), info, null, System.Web.Caching.Cache.NoAbsoluteExpiration, System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.High, null);
}
}
As you are using the as Info conversion at the beginning of your code, if the key isn't present in the cache it'll return a null value anyway, so you don't need to store the null value in cache. Storing null values in the cache doesn't really serve any use, so there's a reason the framework is not allowing you to do that.
Also as a slight side-note, it would be good to create your cache key once, then re-use it rather than reconstructing it every time it's used. For instance:
var key = string.Format("Info_{0}_{1}", id, quantity);
Then just use:
HttpRuntime.Cache[key]
When accessing it, it'll make your code less prone to typo errors.
This is a generic static class/method to solve this quite common problem (null which means "i checked and it doesn't exist, I don't need to check again").
This solution wraps the value like in the other answers.
Usage example
var user = await Cached.Get(
_cache, userName,
async () => _dbContext.LoadAsync<DbUser>(userName)));
Implementation
public class CachedEntry<T>
{
public CachedEntry(T value)
{
Value = value;
}
public T Value { get; }
}
public static async Task<T> Get<T>(IMemoryCache cache,
string key, Func<Task<T>> getDelegate)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
var cachedEntry = cache.Get<CachedEntry<T>>(key);
if (cachedEntry == null)
{
var result = await getDelegate();
cachedEntry = new CachedEntry<T>(result);
cache.Set(key, cachedEntry);
}
return cachedEntry.Value;
}

Error creating an Entity in CRM 2011 - CRM doesn't like OptionSetValue

I'm trying to create an entity in CRM 2011 (not an out of the box kind, but what in CRM 4 would have been called a DynamicEntity... one with my custom attributes). The code below gives me this error and I'm not sure why. This exact same code works if I remove the new_accounttype attribute and try to use another custom attribute.
CRM seems to have taken issue with the "OptionSetValue" being set as the value for that key value pair. new_accounttype is a picklist (or OptionSet in CRM 2011) and that value of 100000003 was pulled from the front end so it's a valid value.
Error: A validation error occurred. The value of 'new_accounttype' on
record of type 'account' is outside the valid range.
What am I doing wrong?
public static void CreateAccount(string accountName, string accountType)
{
//Create properties
KeyValuePairOfstringanyType[] attributes = new KeyValuePairOfstringanyType[2];
attributes[0] = new KeyValuePairOfstringanyType() { key = "name", value = accountName ?? "" };
attributes[1] = new KeyValuePairOfstringanyType() { key = "new_accounttype", value = new OptionSetValue() { Value = 100000003 } };
////Create DynamicEntity
Entity accountToCreate = new Entity();
accountToCreate.LogicalName = "account";
accountToCreate.Attributes = attributes;
try
{
service.Create(accountToCreate);
}
}
I agree that what you have should work fine. This can only mean that the value isn't published or is incorrect. As #glosrob mentions, check that the changes are actually published. Confirm these values by looking at the published form and seeing if your new value is present (and perhaps double check by using IE Developer Tools - hit F12 - and confirm that the value in the select>option object in the HTML contains the integer you expect).
As an aside, your code looks more complex than necessary (IMHO!). I believe this is easier to read an no less efficient:
Try this:
public static void CreateAccount(string accountName, string accountType)
{
////Create DynamicEntity
Entity accountToCreate = new Entity();
accountToCreate.LogicalName = "account";
accountToCreate.Attributes = attributes;
//Append properties
accountToCreate.Attributes.Add("name", accountName ?? "" );
accountToCreate.Attributes.Add("new_accounttype", new OptionSetValue(100000003);
try
{
service.Create(accountToCreate);
}
}
Give this a shot: key = "new_accounttype", value = new OptionSetValue(100000003)

Categories