Reputation: 39298
I'm fetching a list of marketing lists. As I do so, it seems to be a successful operation according to my check using intellisense. When I look for ...Entities[0].Attributes["nick"]
I get an object (with the right data somewhere in it). But I can't access it programmatically (instead I have to click around like a monkey via the pluses to fold out the good stuff).
In fact, I'm getting the entities as supposed to using the code below. The problem is that they aren't Strings
according to the computer. They are of type Microsoft.Xrm.Sdk.AliasedValue
and I don't know how to access the actual nick inside them.
new Contact
{
Name = element.Attributes["nick"] as String,
Mail = element.Attributes["mail"] as String
}
Intellisense says that Value
is in there (and it's the correct value too) but I can't access it by typing .Value
. I suspect that I need to use "as" or something like that but at the moment I'm stuck. Any hints? As
'ing it to String
, which is supposed to work, gives me null
...
I've read this article and several others like it and the way I see it, I'm supposed to be able to access all the fun stuff in there. I can't though...
I've noticed that the following code gets me the data I'm so desperately trying to get but this can't be a professional syntax, can it?! Seriously, it looks like a high school student with ADHD and hangover tried to do that...
new Contact
{
Name = ((Microsoft.Xrm.Sdk.AliasedValue)result.Entities[0].Attributes["nick"]).Value,
Mail = ((Microsoft.Xrm.Sdk.AliasedValue)result.Entities[0].Attributes["mail"]).Value
}
I mean, seriously - this is one ugly piece of code... There's got to be a better way! However, I fear there's not because this discussion seems to be using that syntax as well...
Upvotes: 7
Views: 14770
Reputation: 4263
One more alternative is to use this construct:
public static T GetAliasedAttributeValue<T>(Entity entity, string attributeName)
{
if (entity == null)
return default(T);
AliasedValue fieldAliasValue = entity.GetAttributeValue<AliasedValue>(attributeName);
if (fieldAliasValue == null)
return default(T);
if (fieldAliasValue.Value != null && fieldAliasValue.Value.GetType() == typeof(T))
{
return (T)fieldAliasValue.Value;
}
return default(T);
}
this can be calledeasily as:
Entity entity; //the result
var businessUnitGuid = GetAliasedAttributeValue<Guid>(entity, "bunit.businessunitid");
where entity
is the resulted entity and bunit
is the alias name.
Upvotes: 0
Reputation: 1370
We have a GetValue helper function defined which handles checking for the aliased values for you
public static T GetValue<T>(this Entity entity, string attributeName)
{
if (entity.Attributes.ContainsKey(attributeName))
{
var attr = entity[attributeName];
if (attr is AliasedValue)
{
return GetAliasedValueValue<T>(entity, attributeName);
}
else
{
return entity.GetAttributeValue<T>(attributeName);
}
}
else
{
return default(T);
}
}
This uses an internal extra helper method to get the aliased field value
private static T GetAliasedValueValue<T>(this Entity entity, string attributeName)
{
var attr = entity.GetAttributeValue<AliasedValue>(attributeName);
if (attr != null)
{
return (T)attr.Value;
}
else
{
return default(T);
}
}
It's defined as an extension method on the Entity class so you can use it in the same way as the SDK provided GetAttributeValue - you just have to remember to use the aliased field name.
var myValue = myEntity.GetValue<string>("alias.fieldname")
Upvotes: 0
Reputation: 535
I think you wanted something like this:
//Get Account PrimaryContact details
public static void GetAccountPrimaryContactDetails(Guid accountId, IOrganizationService orgService)
{
var contactFirstName = default(object);
var contactLastName = default(object);
var contactFullName = default(object);
string fetchXML = string.Format(@"<fetch version='1.0' output-format='xml-platform' no-lock='true' mapping='logical'>
<entity name='account'>
<attribute name='name' />
<filter type='and'>
<condition attribute='statuscode' operator='eq' value='1' />
<condition attribute='accountid' operator='eq' value='{0}' />
</filter>
<link-entity name='contact' from='contactid' to='primarycontactid' alias='ab'>
<attribute name='fullname' alias='as_fullname' />
<attribute name='firstname' alias='as_firstname' />
<attribute name='lastname' alias='as_lastname' />
</link-entity>
</entity>
</fetch>", accountId.ToString());
var fetchExp = new FetchExpression(fetchXML);
EntityCollection accountEntity = orgService.RetrieveMultiple(fetchExp);
if (accountEntity.Entities.Count > 0)
{
//Primary Contact Fullname
AliasedValue avContactFullname = accountEntity.Entities[0].GetAttributeValue<AliasedValue>("as_fullname");
if (avContactFullname != null)
contactFullName = avContactFullname.Value;
//Primary Contact Firstname
AliasedValue avContactFirstname = accountEntity.Entities[0].GetAttributeValue<AliasedValue>("as_firstname");
if (avContactFirstname != null)
contactFirstName = avContactFirstname.Value;
//Primary Contact Lastname
AliasedValue avContactLastname = accountEntity.Entities[0].GetAttributeValue<AliasedValue>("as_lastname");
if (avContactLastname != null)
contactLastName = avContactLastname.Value;
Upvotes: 2
Reputation: 5362
Looking at the documention, the Attributes
property of the Entity
object is of type AttributeCollection
, which derives from DataCollection<string,Object>
.
For each attribute in the collection there is a key/value pair.
So for each key ("nick", "mail"), there is a corresponding object
, which could be of any .NET type. You have to cast the object to the correct type (as you've done) to access the properties you're looking for (or else use reflection, which would certainly be uglier, or I suppose in C# 4.0 the dynamic
type, but in that case, you lose compile-time checking); how else would the compiler be able to determine whether an attribute is of type string
/Money
/int
/AliasedValue
/etc.?
As for AliasedValue
, the CRM uses this type to store additional information about the returned value, and since any attribute can be aliased, the Value
property could be of any type (OptionSetValue
, decimal
, string
, Guid
, EntityReference
, etc.). The Value
property is then appropriately also of type object
, so you have to cast this as well to get any additional information about your returned value.
So there's no way around casting, but you can make your code shorter and perhaps cleaner by adding a using
statement at the top of your file and by defining the values of each AliasedValue
prior to the assignment to the Contact
. Regardless, I've included one example of each type of data retrieval; you can judge which is better in your project.
Using casting:
using Microsoft.Xrm.Sdk;
...
var nick = (AliasedValue)result.Entities[0].Attributes["nick"];
var mail = (AliasedValue)result.Entities[0].Attributes["mail"];
var contact = new Contact
{
Name = nick.Value, //Value is of type object; cast again for a more specific type
Mail = mail.Value
};
Using reflection:
var nick = result.Entities[0].Attributes["nick"]
.GetType()
.GetProperty("Value")
.GetValue(result.Entities[0].Attributes["nick"], null);
var mail = result.Entities[0].Attributes["mail"]
.GetType()
.GetProperty("Value")
.GetValue(result.Entities[0].Attributes["mail"], null);
var contact = new Contact
{
Name = nick,
Mail = mail
};
Using dynamic:
dynamic nick = result.Entities[0].Attributes["nick"];
dynamic mail = result.Entities[0].Attributes["mail"];
var contact = new Contact
{
Name = nick.Value, //dynamic figures out the right property at runtime
Mail = mail.Value
};
Upvotes: 8