Getting an early-bound relationship
Asked Answered
J

5

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
Johannejohannes answered 21/2, 2017 at 19:17 Comment(9)
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.Plowman
are any of the relationships that ARE available using crmsvcutil callable using entity.relationship name or something similar?Johannejohannes
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.Plowman
@Plowman got examples? :)Johannejohannes
Nope...give it a shot and add a new question where you need help.Plowman
@l--''''''---------'''''''''''' Did you use any of the answers?Habitation
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.Cohesion
Great point but we're on 5.0Johannejohannes
@l--''''''---------'''''''''''' I don't get this.Even if you were on 6.0 How would you use nameof?Habitation
P
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)) ///////////????
                                   };
Plowman answered 24/2, 2017 at 21:38 Comment(0)
H
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; }
    }
}
Habitation answered 24/2, 2017 at 22:7 Comment(0)
W
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));
Wagonlit answered 2/3, 2017 at 12:5 Comment(2)
The problem (from what I understand) is that "RelationshipSalesOrder.Relationships.SalesOrder_Annotation" does not exist.If it existed ,yes you could do thatHabitation
@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.Plowman
H
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 )
Habitation answered 24/2, 2017 at 21:30 Comment(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 answerHabitation
W
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";
}
Waitress answered 4/10, 2020 at 1:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.