2

I have the following code which works but I believe it is performing multiple look ups just to get the "Account" entity that is associated with my custom "Project" entity.

Is it possible to specify which associated entities you would like populated without having to loop through the initial result set?

    public IList<new_project> GetAssociatedProjectsByPostcode(string postcode)
    {
        FilterExpression filter = new FilterExpression();
        filter.FilterOperator = LogicalOperator.And;
        filter.AddCondition(new ConditionExpression("new_project_zippostalcode", ConditionOperator.Equal, postcode));

        QueryExpression query = new QueryExpression();
        query.EntityName = new_project.EntityLogicalName;
        query.ColumnSet = new ColumnSet(true);
        query.Criteria = filter;

        OrganizationServiceCache serviceCache = new OrganizationServiceCache(MemoryCache.Default, base.CrmConnection);

        using (CachedOrganizationService service = new CachedOrganizationService(CrmConnection, serviceCache))
        using (XrmServiceContext xrmServiceContext = new XrmServiceContext(service))
        {
             //Run the query to return the project entities in a list
             IList<new_project> projects = service.RetrieveMultiple(query)
                                            .Entities
                                            .Select(item => item.ToEntity<new_project>())
                                            .ToList<new_project>();

             //Define the relationships we want populated
             Relationship accountRel = new Relationship("new_account_new_project");

             //We cannot call load property with tracking turned on 
             xrmServiceContext.MergeOption = MergeOption.NoTracking;

             //Loop through the original list and get our associations
             foreach (new_project np in projects)
                 xrmServiceContext.LoadProperty(np, accountRel);

             return projects;
        }            
    }

Is it possible to produce the equivalent with the ServiceContext?

Select Project.Name, Account.Name
From Project
Join Account ON Account.Id = Project.AccountId

EDIT :

After using the link entities as described by James below I now have the following which produces an entity collection with projects and accounts populated in one query but I cannot transfer this flat dataset into the hierarchical object structure.

        public IList<new_project> GetAssociatedProjectsByPostcode(string postcode)
    {
        FilterExpression filter = new FilterExpression();
        filter.FilterOperator = LogicalOperator.And;
        filter.AddCondition(new ConditionExpression("new_project_zippostalcode", ConditionOperator.BeginsWith, PostcodeUtility.RegionFromPostcode(postcode)));

        QueryExpression query = new QueryExpression();
        query.EntityName = new_project.EntityLogicalName;
        query.ColumnSet = new ColumnSet(true);
        query.Criteria = filter;

        query.LinkEntities.Add(new LinkEntity(new_project.EntityLogicalName, Account.EntityLogicalName, "new_associatedaccountid", "accountid", JoinOperator.Inner));
        query.LinkEntities[0].Columns = new ColumnSet(true);
        query.LinkEntities[0].EntityAlias = Account.EntityLogicalName;

        OrganizationServiceCache serviceCache = new OrganizationServiceCache(MemoryCache.Default, base.CrmConnection);

        using (CachedOrganizationService service = new CachedOrganizationService(CrmConnection, serviceCache))
        using (XrmServiceContext xrmServiceContext = new XrmServiceContext(service))
        {
            EntityCollection ec = service.RetrieveMultiple(query);

            //*****************************************************************
            //The entity collection is now populated with the accounts but they
            //are just additional key value pairs
            //e.g. (String)((AliasedValue)ec[0]["account.name"]).Value;
            //*****************************************************************

            //Turn the entity collection into our class structure
            IList<new_project> projects = ec.Entities
                                           .Select(item => item.ToEntity<new_project>())
                                           .ToList<new_project>();
            return projects;
        }
    }
4

2 回答 2

2

是的,这应该很简单,你有几个选择。

  1. FetchXml,这是一种 xml 语法,类似于 tsql 的方法,它不具备所有功能,但您可以使用MSDNlink-entity中所示的方式进行连接。

  2. QueryExpression, 有一个LinkEntities可以像这个MSDN一样使用的属性。

  3. 您可以发出一个RetrieveRequest,填充这里RelatedEntitiesQuery显示的。


编辑

因此,实体集合看起来像是返回了我期望的结果(例如这些帐户的帐户和值)-我认为 typeof(ec[0]) 是实体?

所以它只是转换为错误的早期界限。

我没有用过很多 linq 来进行 crm,所以我开始猜测,但是从这个例子来看。

您可能只需要:

  1. EnableProxyTypes 在您的服务上,显然这是完整的早期绑定支持所必需的。
  2. 将实体转换为您的早期绑定类型。

例如(来自示例):

_serviceProxy.EnableProxyTypes();

Account retrievedAccount = (Account)_serviceProxy.Retrieve("account", _accountId, cols);

retrievedAccount.Address1_PostalCode = "98052";

Retrieve 只返回一个实体(EntityCollection.Entities 的类型),并且强制转换似乎在这里工作。

于 2012-09-13T19:21:14.310 回答
1

编辑自 James Wood 的评论... 您可以使用Retrieve Request检索实体的子实体子实体。您必须拥有父实体的 GUID 才能使其正常工作...另一方面,您可以通过将列添加到链接实体的列集合来包含子实体的值。您可以指定别名或只接受默认名称。

我已经广泛使用这些扩展方法来访问父实体的别名值:

    // This needs to be placed in a public static class and it's namespace added as a using to whatever class you'd like to use it in

    /// <summary>
    /// Returns the Aliased Value for a column specified in a Linked entity
    /// </summary>
    /// <typeparam name="T">The type of the aliased attribute form the linked entity</typeparam>
    /// <param name="entity"></param>
    /// <param name="attributeName">The aliased attribute from the linked entity.  Can be preappeneded with the
    /// linked entities logical name and a period. ie "Contact.LastName"</param>
    /// <returns></returns>
    public static T GetAliasedValue<T>(this Entity entity, string attributeName)
    {
        string aliasedEntityName = SplitAliasedAttributeEntityName(ref attributeName);

        AliasedValue aliased;
        foreach (var attribute in entity.Attributes.Values)
        {
            aliased = attribute as AliasedValue;
            if(entity.IsAttributeAliasedValue(attributeName, aliasedEntityName, aliased))
            {
                try
                {
                    return (T)aliased.Value;
                }
                catch (InvalidCastException)
                {
                    throw new InvalidCastException(
                        String.Format("Unable to cast attribute {0}.{1} from type {2} to type {3}",
                                aliased.EntityLogicalName, aliased.AttributeLogicalName,
                                typeof(T).Name, aliased.Value.GetType().Name));
                }
            }
        }

        throw new Exception("Aliased value with attribute " + attributeName +
            " was not found!  Only these attributes were found: " + String.Join(", ", entity.Attributes.Keys));
    }

    /// <summary>
    /// Returns the Aliased Value for a column specified in a Linked entity, returning the default value for 
    /// the type if it wasn't found
    /// </summary>
    /// <typeparam name="T">The type of the aliased attribute form the linked entity</typeparam>
    /// <param name="entity"></param>
    /// <param name="attributeName">The aliased attribute from the linked entity.  Can be preappeneded with the
    /// linked entities logical name and a period. ie "Contact.LastName"</param>
    /// <returns></returns>
    public static T GetAliasedValueOrDefault<T>(this Entity entity, string attributeName)
    {
        T value;
        if (entity.HasAliasedAttribute(attributeName))
        {
            value = entity.GetAliasedValue<T>(attributeName);
        }
        else
        {
            value = default(T);
        }
        return value;
    }

    private static bool IsAttributeAliasedValue(this Entity entity, string attributeName, string aliasedEntityName, AliasedValue aliased)
    {
        bool value =
       (aliased != null &&
            (aliasedEntityName == null || aliasedEntityName == aliased.EntityLogicalName) &&
            aliased.AttributeLogicalName == attributeName);


        /// I believe there is a bug in CRM 2011 when dealing with aggregate values of a linked entity in FetchXML.
        /// Even though it is marked with an alias, the AliasedValue in the Attribute collection will use the 
        /// actual CRM name, rather than the aliased one, even though the AttributeCollection's key will correctly
        /// use the aliased name.  So if the aliased Attribute Logical Name doesn't match the assumed attribute name
        /// value, check to see if the entity contains an AliasedValue with that key whose attribute logical name 
        /// doesn't match the key (the assumed bug), and mark it as being the aliased attribute
        if (!value && aliased != null && entity.Contains(attributeName))
        {
            var aliasedByKey = entity[attributeName] as AliasedValue;
            if (aliasedByKey != null && aliasedByKey.AttributeLogicalName != attributeName && 
                Object.ReferenceEquals(aliased, aliasedByKey))
            {
                value = true;
            }
        }

        return value;
    }

    /// <summary>
    /// Returns the Aliased Value for a column specified in a Linked entity
    /// </summary>
    /// <typeparam name="T">The type of the aliased attribute form the linked entity</typeparam>
    /// <param name="entity"></param>
    /// <param name="attributeName">The aliased attribute from the linked entity.  Can be preappeneded with the
    /// linked entities logical name and a period. ie "Contact.LastName"</param>
    /// <returns></returns>
    public static bool HasAliasedAttribute(this Entity entity, string attributeName)
    {
        string aliasedEntityName = SplitAliasedAttributeEntityName(ref attributeName);
        return entity.Attributes.Values.Any(a =>
            entity.IsAttributeAliasedValue(attributeName, aliasedEntityName, a as AliasedValue));
    }

    /// <summary>
    /// Handles spliting the attributeName if it is formated as "EntityAliasedName.AttributeName",
    /// updating the attribute name and returning the aliased EntityName
    /// </summary>
    /// <param name="attributeName"></param>
    /// <param name="aliasedEntityName"></param>
    private static string SplitAliasedAttributeEntityName(ref string attributeName)
    {
        string aliasedEntityName = null;
        if (attributeName.Contains('.'))
        {
            var split = attributeName.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
            if (split.Length != 2)
            {
                throw new Exception("Attribute Name was specified for an Alaised Value with " + split.Length +
                " split parts, and two were expected.  Attribute Name = " + attributeName);
            }
            aliasedEntityName = split[0];
            attributeName = split[1];
        }

        return aliasedEntityName;
    }
于 2012-09-20T20:40:02.003 回答