在一个查询中加载关联实体

时间:2012-09-13 12:15:41

标签: c# dynamics-crm-2011

我有以下代码可以使用,但我相信它只是为了获得与我的自定义“项目”实体相关联的“帐户”实体而执行多次查找。

是否可以指定要填充哪些关联实体而无需循环初始结果集?

    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;
        }            
    }

是否可以使用ServiceContext生成等效文件?

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

编辑:

在使用James下面描述的链接实体后,我现在有以下内容生成一个实体集合,其中包含在一个查询中填充的项目和帐户,但我无法将此平面数据集传输到分层对象结构中。

        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;
        }
    }

2 个答案:

答案 0 :(得分:2)

是的,这应该是非常直接的,你有几个选择。

  1. FetchXml,这是一个xml语法,类似于tsql的方法,它没有所有功能,但你可以与link-entity进行连接,如MSDN所示。

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

  3. 您可以发出RetrieveRequest,填充RelatedEntitiesQuery,如图所示here


  4. 修改

    所以实体集合看起来像它返回我期望的结果(例如那些帐户的帐户和值) - 我认为typeof(ec [0])是实体?

    所以它只是转换到出错的早期界限。

    我非常习惯使用linq,所以我开始猜测,但是从这个example开始。

    您可能只需要:

    1. 您的服务上的EnableProxyTypes,显然这是完全早期绑定支持所必需的。
    2. 将实体投射到您的早期绑定类型。
    3. 例如(来自样本):

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

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

答案 1 :(得分:1)

詹姆斯伍德的评论编辑... 您可以使用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;
    }