Overview

An MsCrmClient library provides the base abstract class CrmClientBase.
This class contains abstract property

protected abstract IWcfCrmClient WcfClient { get; } 
which should be overridden to provide an IWcfCrmClient implementation.

IWcfCrmClient defines methods to comunicate with MS CRM WCF Service:

public interface IWcfCrmClient
{   
    Guid Create(Entity entity);   
    void Update(Entity entity);   
    void Delete(string entityName, System.Guid id);   
    EntityCollection RetrieveMultiple(QueryBase query);   
    OrganizationResponse Execute(OrganizationRequest request);
    void Close();
}
Client example (OrganizationServiceClient - service reference client):

    using System;
    using CrmClient;
    using Microsoft.Xrm.Sdk;
    using Microsoft.Xrm.Sdk.Query;
    using MsCrmClientTest.MSCRM;

    public class OrgCrmClient : CrmClientBase
    {
        private class WcfCrmClient : IWcfCrmClient
        {
            private OrganizationServiceClient _client;

            public Guid Create(Entity entity)
            {
                return _client.Create(entity);
            }
            public void Update(Entity entity)
            {
                _client.Update(entity);
            }
            public void Delete(string entityName, Guid id)
            {
                _client.Delete(entityName, id);
            }
            public EntityCollection RetrieveMultiple(QueryBase query)
            {
                return _client.RetrieveMultiple(query);
            }
            public OrganizationResponse Execute(OrganizationRequest request)
            {
                return _client.Execute(request);
            }
            public void Close()
            {
                _client.Close();
            }
            public WcfCrmClient()
            {
                _client = new OrganizationServiceClient();
            }
        }
        private IWcfCrmClient _wcfClient;
        protected override IWcfCrmClient WcfClient
        {
            get
            {
                if (_wcfClient == null)
                    _wcfClient = new WcfCrmClient();
                return _wcfClient;
            }
        }
    }

Mapping

Entities mapping

To map classes to CRM entities (data contracts definition) you should use standart attributes:
  • EntityLogicalNameAttribute(string name) defines the name of mapped CRM entity. Using for class
  • AttributeLogicalNameAttribute(string name) defines the name of mapped CRM property. Using for property
Each data contract should be inherited from CrmDataContractBase and override Id property.
Mapping example:

[EntityLogicalName("systemuser")]
public class User : CrmDataContractBase
{
    [AttributeLogicalName("systemuserid")]
    public override Guid Id { get; set; }
    [AttributeLogicalName("fullname")]
    public string Name { get; set; }
    [AttributeLogicalName("parentsystemuserid")]
    public EntityReference Сhief { get; set; }
    [AttributeLogicalName("caltype")]
    public OptionSetValue CALType
}
Note
  • If property is reference to another entity (like Chief in example) it should be EntityReference type
  • If property is option value (like CALType in example) it should be OptionSetValue type.

Options sets mapping

Options set contract must be inherited from the CrmOptionsSetBase class and marked with the EntityLogicalName attribute. In the attrubute constructor, must be specified name of the option set.
Example:

[EntityLogicalName("connectionrole_category")]
public class ConnectionRoleCategoryEnum : CrmOptionsSetBase
{ }
The library contains method, to get options sets:

public T OptionsSet<T>()
OptionsSet retrieve example:

var optionSet = _client.OptionsSet<ConnectionRoleCategoryEnum>();

Add, update, delete operations

[EntityLogicalName("new_nsi")]
public class NSI : ICrmDataContract
{
    [AttributeLogicalName("new_nsiid")]
    public override Guid Id { get; set; }
    [AttributeLogicalName("new_name")]
    public string Name { get; set; }
}
//Add
var newnsi = new NSI { Name = "Test NSI" };
_client.Add(newnsi); //The 'Add' method will set 'Id' property
//Update
newnsi.Name = "Test NSI 2";
_client.Update(newnsi);
//Delete
_client.Delete(newnsi);

Querying and Linq

The main target of this project is to provide Linq-like fluent sintax to build queries. The library contains set of extensions to provide it:
  • Select
  • Where
  • Join
  • Order
  • Paging
  • Distinct

Querying

To get data from CRM, the client contains a method:

public ICrmQueryable<T> Query<T>()
where T is a data contract type.
In fact the request will be executed on collection iteration (like in EF).

Linq

The Linq extension methods could be called only after Query method call

Select

Anonimous result type

var users = _client.Query<CrmUser>()
    .CrmSelect(u => new
    {
        u.Id,
        u.Name,
        Test = 1
    })
    .ToList();
Class result type

var users2 = _client.Query<CrmUser>()
    .CrmSelect(u => new TestUser()
    {
        Id = u.Id,
        FullName = u.Name,
        Test = 1
    })
    .ToList();

Where

//simple
var user = _client.Query<CrmUser>()
    .CrmWhere(i => i.Id == _directorUserId)
    .Single();

var list = new[] { _directorUserId };
// in
var filteredUsers = _client.Query<CrmUser>()
               .CrmWhere(i => list.Contains(i.Id))
               .ToList();
// not in
filteredUsers = _client.Query<CrmUser>()
               .CrmWhere(i => !list.Contains(i.Id))
               .ToList();
//like
var users = _client.Query<CrmUser>().ToList();
var firstUser = users.First(i => i.Name.Contains(" ")).Name.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
// like text%
var user = _client.Query<CrmUser>()
    .CrmWhere(i => i.Name.StartsWith(firstUser[0]))
    .ToList();
// like %text
user = _client.Query<CrmUser>()
    .CrmWhere(i => i.Name.EndsWith(firstUser[0]))
    .ToList();
// like %text%
user = _client.Query<CrmUser>()
   .CrmWhere(i => i.Name.Contains(firstUser[0]))
   .ToList();
// not like text%
user = _client.Query<CrmUser>()
    .CrmWhere(i => !i.Name.StartsWith(firstUser[0]))
    .ToList();
// not like %text
user = _client.Query<CrmUser>()
    .CrmWhere(i => !i.Name.EndsWith(firstUser[0]))
    .ToList();
// not like %text%
user = _client.Query<CrmUser>()
    .CrmWhere(i => !i.Name.Contains(firstUser[0]))
    .ToList();
As you can see, all EF features are supported. But the library provides else one additional feature - compound key condition (for EF this feature provides by EFCompoundkeyWhere library):
var users = _client.Query<CrmUser>().ToList();
var directors = users.Where(u => u.Director != null).Select(u => new { u.Director.Id, u.Director.Name }).Take(2);
var users2 = _client.Query<CrmUser>()
    .CrmWhere(ExpressionType.Or, directors, (u, d) => u.Id == d.Id && u.Name == d.Name, (pn, o) =>
        {
            switch (pn)
            {
                case "Id":
                    return o.Id;
                case "Name":
                    return o.Name;
                default:
                    return null;
            }
        })
    .ToList();

Order

var users = _client.Query<CrmUser>()
    .CrmOrderBy(i => i.Name)
    .CrmOrderByDescending(i => i.Id)
    .ToList();
Result set will be ordered by Name asc, then by Id desc

Distinct

var users = _client.Query<CrmUser>()
    .CrmDistinct()
    .ToList();

Join

Join operation is more complex than others.
For example: You need to join Users with Users to get user’s info and his chief’s info:
var users = _client.Query<CrmUser>()
    .CrmJoin(_client.Query<CrmUser>(), s => s.Chief.Id, d => d.Id, (s, d) => new { s.Id, s.Name, ChiefId = d.Id, ChiefFullName = d.Name })
    .ToList();

or

var users = _client.Query<CrmUser>()
    .CrmLeftJoin(_client.Query<CrmUser>(), s => s.Chief.Id, d => d.Id, (s, d) => new { s.Id, s.Name, ChiefId = d.Id, ChiefFullName = d.Name })
    .ToList();
this request will be executed correctly. But if you will look closer on s.Chief.Id, the Id property of CrmUser is mapped to 'systemuserid', but to join uses 'parentsystemuserid' (s.Chief map)... this behavior implemented in the library core, and it helps to aviod the problem when User will be mapped to it's self.

Another problem is conditions for joined entities. In pure CRM query, that conditions are included in Link itself, and that behavior could not be avoided.
To define a condition for joined entity, the condition should be defined for entity request with Where method:

var users = _client.Query<CrmUser>()
    .CrmJoin(_client.Query<CrmUser>().CrmWhere(u => u.Id == _directorUserId), s => s.Chief.Id, d => d.Id, (s, d) => new { s.Id, s.Name, ChiefId = d.Id, ChiefFullName = d.Name })
    .ToList();

or

var users = _client.Query<CrmUser>()
    .CrmLeftJoin(_client.Query<CrmUser>().CrmWhere(u => u.Id == _directorUserId), s => s.Chief.Id, d => d.Id, (s, d) => new { s.Id, s.Name, ChiefId = d.Id, ChiefFullName = d.Name })
    .ToList();
this request will be executed correctly.
Note you can add after CrmWhere othe linq methods, like CrmSelect and other, but they will not take effect

NoLock

To execute query on CRM with (nolock) option:

var users = _client.Query<CrmUser>()
    .CrmNoLock()
    .ToList();

Paging

To execute paged requests, the library provides a method

List<T> CrmGetPage(int pageNumber, int pageSize, out int totalCount, out bool moreRecordsExists)
Example:

int total;
bool moreExists;
var users = _client.Query<CrmUser>()
    .CrmGetPage(1, 10, out total, out moreExists);
Note: All these examples could be found in Test class in MsCrmClientTest project.

Last edited Apr 19, 2013 at 7:56 AM by SetSergey, version 30

Comments

No comments yet.