应该使用 ToEntity<T> 而不是演员表吗?

Should ToEntity<T> Be Used Instead Of A Cast?

Xrm Sdk defines一个ToEntity<T>方法。我一直用它从 CRM 获取我早期绑定的实体。今天我正在审查一些代码,看到实体刚刚被转换:

var contact = service.Retrieve("contact", id, new ColumnSet()) as Contact;

我什至不知道这是可能的。甚至还需要 ToEntity 调用吗?

顺便提一下,这不是特定的转换,而是转换,转换的行为与直接转换略有不同。

As

You can use the as operator to perform certain types of conversions between compatible reference types or nullable types...The as operator is like a cast operation. However, if the conversion isn't possible, as returns null instead of raising an exception.

我假设您的 Contact 是由 CrmSvcUtil 创建的 class,例如public partial class Contact : Microsoft.Xrm.Sdk.Entityservice.RetrieveIOrganizationService.Retrieve,return 类型为 Entity

Contact 是基数 class Entity 的派生 class。您不能将基础 class 转换为更具体的派生 class(请参阅 Is it possible to assign a base class object to a derived class reference with an explicit typecast in C#?)。如果您尝试执行从 EntityContact 的强制转换,您将得到一个异常,并且转换将 return 一个空对象。

包含来自 CrmSvcUtil 的生成代码的示例,但与 CRM 没有实际连接。

var entity = new Entity();

Console.WriteLine($"Type of local entity: {entity.GetType()}");

Console.WriteLine($"Local entity as Contact is null? {entity as Contact == null}");

输出:

Type of local entity: Microsoft.Xrm.Sdk.Entity
Local entity as Contact is null? True

所以给定 Retrieve returns 一个 Entity,它不能转换为 Contact,你的代码行 (var contact = service.Retrieve("contact", id, new ColumnSet()) as Contact; ) 甚至工作?

嗯,这很神奇。显然,如果您在应用程序中包含来自 CrmSvcUtil 的 GeneratedCode,Retrieve 函数 return 是特定派生的 classes 而不是通用 Entity.

包含来自 CrmSvcUtil 的生成代码的示例:

CrmServiceClient service = new CrmServiceClient(ConfigurationManager.ConnectionStrings["Crm"].ConnectionString);

Contact c = new Contact()
{
    LastName = "Test"
};

Guid contactId = service.Create(c);

var response = service.Retrieve("contact", contactId, new ColumnSet());

Console.WriteLine($"Type of response from CRM: {response.GetType()}");

Console.WriteLine($"Response from CRM as contact is null? {response as Contact == null}");

输出:

Type of response from CRM: Contact
Response from CRM as contact is null? False

不包含生成代码的示例:

CrmServiceClient service = new CrmServiceClient(ConfigurationManager.ConnectionStrings["Crm"].ConnectionString);

Entity c = new Entity("contact");
c["lastname"] = "Test";

Guid contactId = service.Create(c);

var response = service.Retrieve("contact", contactId, new ColumnSet());

Console.WriteLine($"Type of response: {response.GetType()}");

输出:

Type of response: Microsoft.Xrm.Sdk.Entity

回到你的问题。如果您在项目中包含生成的代码,鉴于 Retrieve 正在 returning 一个 Contact 无论如何您可以只做一个简单的转换(例如 (Contact)service.Retrieve(...))或转换(as)。就 ToEntity 的作用而言,它实际上并没有进行转换或转换。它创建一个新对象并执行浅表复制等一些事情。因此,如果满足您的需要,请使用它,但没有它您可能会逃脱。

反编译代码:

public T ToEntity<T>() where T : Entity
{
    if (typeof(T) == typeof(Entity))
    {
        Entity entity = new Entity();
        this.ShallowCopyTo(entity);
        return entity as T;
    }
    if (string.IsNullOrWhiteSpace(this._logicalName))
    {
        throw new NotSupportedException("LogicalName must be set before calling ToEntity()");
    }
    string text = null;
    object[] customAttributes = typeof(T).GetCustomAttributes(typeof(EntityLogicalNameAttribute), true);
    if (customAttributes != null)
    {
        object[] array = customAttributes;
        int num = 0;
        if (num < array.Length)
        {
            EntityLogicalNameAttribute entityLogicalNameAttribute = (EntityLogicalNameAttribute)array[num];
            text = entityLogicalNameAttribute.LogicalName;
        }
    }
    if (string.IsNullOrWhiteSpace(text))
    {
        throw new NotSupportedException("Cannot convert to type that is does not have EntityLogicalNameAttribute");
    }
    if (this._logicalName != text)
    {
        throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "Cannot convert entity {0} to {1}", new object[]
        {
            this._logicalName,
            text
        }));
    }
    T t = (T)((object)Activator.CreateInstance(typeof(T)));
    this.ShallowCopyTo(t);
    return t;
}

它总是那样工作,请查看来自 here

的 CRM 2011 示例代码
ColumnSet cols = new ColumnSet(new String[] { "name", "address1_postalcode", "lastusedincampaign", "versionnumber" });
Account retrievedAccount = (Account)_serviceProxy.Retrieve("account", _accountId, cols);
Console.Write("retrieved ");

这就是您必须对 IOrganizationService 执行 EnableProxyTypes(); 的原因。基本上,如果您这样做,所有调用都将 return 早期绑定类型,而不是 Entity 对象(当然,早期绑定是从 Entity 继承的,但您知道我的意思)。这只是一个关于从 CRM 获取数据的功能。

这与 ToEntity<>() 无关,因为您仍然不能这样做:

var account = new Entity("account");
var earlyBoundAccount = account as Account; //this will result in NULL

因此,如果您有实体(例如在插件目标或 PostImage 中),您仍然必须使用 ToEntity 将其转换为早期绑定。

更新: 我深入挖掘并检查了 EnableProxyTypes 的作用——它只是使用 DataContractSerializerOperationBehavior class to inject it's own IDataContractSurrogate to handle serialization/deserialization of the response (example how it can be used can be found here)。通过查看 CRM 的反序列化源,您可以亲眼看到反序列化是如何实现的:

object IDataContractSurrogate.GetDeserializedObject(object obj, Type targetType)
{
    bool supportIndividualAssemblies = this._proxyTypesAssembly != null;
    OrganizationResponse organizationResponse = obj as OrganizationResponse;
    if (organizationResponse != null)
    {
        Type typeForName = KnownProxyTypesProvider.GetInstance(supportIndividualAssemblies).GetTypeForName(organizationResponse.ResponseName, this._proxyTypesAssembly);
        if (typeForName == null)
        {
            return obj;
        }
        OrganizationResponse organizationResponse2 = (OrganizationResponse)Activator.CreateInstance(typeForName);
        organizationResponse2.ResponseName = organizationResponse.ResponseName;
        organizationResponse2.Results = organizationResponse.Results;
        return organizationResponse2;
    }
    else
    {
        Entity entity = obj as Entity;
        if (entity == null)
        {
            return obj;
        }
        Type typeForName2 = KnownProxyTypesProvider.GetInstance(supportIndividualAssemblies).GetTypeForName(entity.LogicalName, this._proxyTypesAssembly);
        if (typeForName2 == null)
        {
            return obj;
        }
        Entity entity2 = (Entity)Activator.CreateInstance(typeForName2);
        entity.ShallowCopyTo(entity2);
        return entity2;
    }
}

所以基本上来自 KnownProxyTypes 的类型是通过实体逻辑名称获得的,并使用 Activator 实例化。再次 - 这仅适用于您为其启用代理类型的 IOrganizationService(据我所知,如果代理在同一个程序集中 IOrganizationService 被实例化,默认情况下启用,即使您没有明确调用它,但这个我不是 100% 确定)