15

When I am linking annotations to a specific entity, rather than creating a relationship like so:

var associateRequest = new AssociateRequest
{
    Target = new EntityReference(SalesOrder.EntityLogicalName, salesOrderGuid),
    RelatedEntities = new EntityReferenceCollection
    {
        new EntityReference(Annotation.EntityLogicalName, noteGuid),
    },
    Relationship = new Relationship("SalesOrder_Annotation")
};

Is it possible to reference the relationship in a strongly typed way:

var associateRequest = new AssociateRequest
{
    Target = new EntityReference(SalesOrder.EntityLogicalName, salesOrderGuid),
    RelatedEntities = new EntityReferenceCollection
    {
        new EntityReference(Annotation.EntityLogicalName, noteGuid)
    },
    Relationship = SalesOrder.Relationships.SalesOrder_Annotation // <----- ???
};

This would be similar to being able to get the logicalname at develop time:

SalesOrder.EntityLogicalName

Can we reference the specific 1:N relationship the same way:

SalesOrder.Relationships.SalesOrder_Annotation
Alex Gordon
  • 57,446
  • 287
  • 670
  • 1,062
  • What is the type returned by `SalesOrder.Relationships.SalesOrder_Annotation`? If it isn't `Relationship` then no, you can't call it. I have to ask because `SalesOrder.Relationships.SalesOrder_Annotation` isn't available using the standard CrmSvcUtil.exe code gen tool - so it much be something custom. – Nicknow Feb 22 '17 at 04:46
  • are any of the relationships that ARE available using crmsvcutil callable using entity.relationship name or something similar? – Alex Gordon Feb 22 '17 at 15:13
  • Nope. You would have to create an extension to generate `const` or `readonly` strings with the relationship names. Or write a method to read the relationship name from the code attribute outputted by `CrmSvcUtil.exe`. – Nicknow Feb 22 '17 at 16:30
  • @Nicknow got examples? :) – Alex Gordon Feb 22 '17 at 16:35
  • Nope...give it a shot and add a new question where you need help. – Nicknow Feb 22 '17 at 16:44
  • 1
    @l--''''''---------'''''''''''' Did you use any of the answers? – George Vovos Feb 27 '17 at 16:25
  • I know it's not the answer you're looking for but the new 'nameof()' keyword in C# 6 saves you in situations like this by making the string name a type name. At least it will give you compile time errors and auto rename if you change the type name. I would just give that a try and stick to the original design. – Michael Puckett II Feb 28 '17 at 05:43
  • Great point but we're on 5.0 – Alex Gordon Mar 02 '17 at 15:13
  • @l--''''''---------'''''''''''' I don't get this.Even if you were on 6.0 How would you use nameof? – George Vovos Mar 03 '17 at 16:20

5 Answers5

5

The value you are looking for is stored in a code attribute, RelationshipSchemaNameAttribute, if you generate your code using the standard CrmSvcUtil.exe application provided in the SDK (\SDK\Bin\CrmSvcUtil.exe). I've validated this code in a console application using the early bound entity class file provided in the SDK (\SDK\SampleCode\CS\HelperCode\MyOrganizationCrmSdkTypes.cs).

Call the method as follows (per your example):

var relationship = GetRelationship<SalesOrder>(nameof(SalesOrder.SalesOrder_Annotation))

Or if you want to return the actual string value:

var relationshipName = GetRelationshipSchemaName<SalesOrder>(nameof(SalesOrder.SalesOrder_Annotation))

Add this code to a helper class in your application:

public static string GetRelationshipSchemaName<T>(string relationshipPropertyName) where T:Entity
{
    return typeof (T).GetProperties()
        .FirstOrDefault(x => x.Name == relationshipPropertyName)
        .GetCustomAttributes()
        .OfType<RelationshipSchemaNameAttribute>()
        .FirstOrDefault()
        ?.SchemaName;            
}

public static Relationship GetRelationship<T>(string relationshipPropertyName) where T : Entity
{
    return new Relationship(typeof(T).GetProperties()
        .FirstOrDefault(x => x.Name == relationshipPropertyName)
        .GetCustomAttributes()
        .OfType<RelationshipSchemaNameAttribute>()
        .FirstOrDefault()
        ?.SchemaName);
}

This is what your updated code would look like:

var associateRequest = new AssociateRequest
                                   {
                                       Target =
                                           new EntityReference(
                                               SalesOrder.EntityLogicalName,
                                               salesOrderGuid),
                                       RelatedEntities =
                                           new EntityReferenceCollection
                                               {
                                                   new EntityReference(
                                                       Annotation
                                                           .EntityLogicalName,
                                                       noteGuid)
                                               },
                                       Relationship = GetRelationship<SalesOrder>(nameof(SalesOrder.SalesOrder_Annotation)) ///////////????
                                   };
Nicknow
  • 7,154
  • 3
  • 22
  • 38
4

On second thought ,my t4 template answer seems an overkill
You can use expression trees and an extension method to easily get what you need

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace ConsoleApplication9
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Relationship r = new Class1().GetRelationShip(s => s.RelationShipProperty);
            Console.WriteLine(r.Name);
            System.Console.ReadLine();
        }
    }

    public static class MyExtention
    {
        public static Relationship GetRelationShip<T, TProperty>(this T t, Expression<Func<T, TProperty>> expression)
        {
            return new Relationship(((expression.Body as MemberExpression).Member as PropertyInfo)
                    .GetCustomAttributes(typeof(RelationshipAttribute))
                    .Select(a=>(RelationshipAttribute)a)
                    .First().Name
                    );
        }
    }

    public class RelationshipAttribute : System.Attribute
    {
        public string Name { get; set; }

        public RelationshipAttribute(string name)
        {
            Name = name;
        }
    }

    public class Relationship
    {
        public string Name { get; set; }

        public Relationship(string name)
        {
            Name = name;
        }
    }

    public class Class1
    {
        [Relationship("RelationShipA")]
        public List<int> RelationShipProperty { get; set; }
    }
}
George Vovos
  • 7,563
  • 2
  • 22
  • 45
4

I'm not sure whether I got the question right. Wouldn't C# 6.0 feature nameof(...) do the thing?

ie.

new Relationship(nameof(RelationshipSalesOrder.Relationships.SalesOrder_Annotation));
MKoperski
  • 76
  • 2
  • The problem (from what I understand) is that "RelationshipSalesOrder.Relationships.SalesOrder_Annotation" does not exist.If it existed ,yes you could do that – George Vovos Mar 02 '17 at 14:35
  • @GeorgeVovos is correct. There is no `RelationshipSalesOrder.Relationships.SalesOrder_Annotation` and, even if it existed, it would not necessarily match the string value that the `Relationship` constructor needs. – Nicknow Mar 02 '17 at 15:14
3

From what you said, your generated classes have an attribute with the name of your relationship.
All you need is a t4 template that generates a class with strongly typed properties for your Relationships

Let's say you have the following code in your project

namespace ConsoleApplication9
{
    public class RelationshipAttribute : System.Attribute
    {
        public string Name { get; set; }

        public RelationshipAttribute(string name) { Name = name; }
    }

    [Relationship("RelationShipA")]
    public class Class1 { }

    [Relationship("RelationShipB")]
    public class Class2 { }

    [Relationship("RelationShipC")]
    public class Class3 { }

}

This template

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="$(TargetPath)" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Reflection" #>
<#@ output extension=".cs" #>

namespace YourNameSpace
{
   public static class Relationships
   {
      <# 
         var types = typeof(ConsoleApplication9.RelationshipAttribute).Assembly
                     .GetTypes()
                     .Select(t => new { t.Name, Value = t.GetCustomAttribute(typeof(ConsoleApplication9.RelationshipAttribute)) })
                     .Where(t => t.Value != null)
                     .Select(t=> new { t.Name,Value= ((ConsoleApplication9.RelationshipAttribute)t.Value).Name })
                     .ToList();

                 foreach (var attr in types)
  { #>
 public static class  <#=  attr.Name #>
        {
            public const string <#=  attr.Value #> = "<#=  attr.Value #>";
        }
      <# }

  #>}
}

will produce the following .cs file

namespace YourNameSpace
{
   public static class Relationships
   {
         public static class  Class1
         {
           public const string RelationShipA = "RelationShipA";
         }
         public static class  Class2
         {
           public const string RelationShipB = "RelationShipB";
         }
         public static class  Class3
         {
           public const string RelationShipC = "RelationShipC";
         }
   }
}

Then you can use it like

Relationship = new Relationship(Relationships.Class1.RelationShipA )
George Vovos
  • 7,563
  • 2
  • 22
  • 45
  • 1
    @Downvoter Not sure this answer deserved the downvote...It might be more complex than the others but other other hand it has no performance penalty... If The OP wants to use the code it a loop it is probably the best answer – George Vovos Mar 01 '17 at 16:46
1

The Early Bound Generator in the XrmToolBox will generate these relationship names for you.

public static class Fields
{
    public const string AccountId = "accountid";
    public const string AccountRoleCode = "accountrolecode";
    public const string Address1_AddressId = "address1_addressid";
    // *** SNIP *** 
    public const string YomiLastName = "yomilastname";
    public const string YomiMiddleName = "yomimiddlename";
    public const string business_unit_contacts = "business_unit_contacts";
    public const string contact_customer_accounts = "contact_customer_accounts";
    public const string Referencingcontact_customer_contacts = "contact_customer_contacts";
    public const string Referencingcontact_master_contact = "contact_master_contact";
    public const string contact_owning_user = "contact_owning_user";
    public const string lk_contact_createdonbehalfby = "lk_contact_createdonbehalfby";
    public const string lk_contact_modifiedonbehalfby = "lk_contact_modifiedonbehalfby";
    public const string lk_contactbase_createdby = "lk_contactbase_createdby";
    public const string lk_contactbase_modifiedby = "lk_contactbase_modifiedby";
    public const string system_user_contacts = "system_user_contacts";
}
Daryl
  • 18,592
  • 9
  • 78
  • 145