terça-feira, 17 de fevereiro de 2009

.NET Framework 3.5 - LINQ to DataSet

LINQ to DataSet faz com que a busca de dados em [objetos DataSet] se tornem ainda mais fáceis e rápidas.

Este recurso traz maior produtividade e flexibilidade para os desenvolvedores já que trabalham com [Queries] na sua própria linguagem de programação.

LINQ to DataSet expõe, primariamente, os métodos adicionais nas classes [DataRowExtensions] and [DataTableExtensions]. Também se utiliza da arquitetura do ADO.NET 2.0, não tendo a intenção da substituir o ADO.NET 2.0 no código da aplicação.

O relacionamento do [LINQ to DataSet] com o [ADO.NET 2.0] e o [Data Store] é ilustrado no diagrama abaixo:



Querying DataSets Usando LINQ to DataSet

No exemplo abaixo podemos notar a facilidade de busca e manipulação dos dados em um DataSet do ADO.NET 2.0. Como a classe [DataTable] não implementa a interface [IEnumerable(T)], chamamos o método [AsEnumerable] para usar o DataTable com uma [fonte] para a cláusula [From] da LINQ query.

O exemplo busca todos os pedidos [online] da tabela [SalesOrderHeader] e mostra os atributos [order ID], [order date], e [order number] .


[Visual Basic]
' Fill the DataSet.
Dim ds As New DataSet()
ds.Locale = CultureInfo.InvariantCulture
' See the FillDataSet method in the Loading Data Into a DataSet topic.
FillDataSet(ds)

Dim orders As DataTable = ds.Tables("SalesOrderHeader")

Dim query = _
From order In orders.AsEnumerable() _
Where order.Field(Of Boolean)("OnlineOrderFlag") = True _
Select New With { _
.SalesOrderID = order.Field(Of Integer)("SalesOrderID"), _
.OrderDate = order.Field(Of DateTime)("OrderDate"), _
.SalesOrderNumber = order.Field(Of String)("SalesOrderNumber") _
}

For Each onlineOrder In query
Console.Write("Order ID: " & onlineOrder.SalesOrderID)
Console.Write(" Order date: " & onlineOrder.OrderDate)
Console.WriteLine(" Order number: " & onlineOrder.SalesOrderNumber)
Next



[C#]
// Fill the DataSet.
DataSet ds = new DataSet();
ds.Locale = CultureInfo.InvariantCulture;
FillDataSet(ds);

DataTable orders = ds.Tables["SalesOrderHeader"];

var query =
from order in orders.AsEnumerable()
where order.Field("OnlineOrderFlag") == true
select new
{
SalesOrderID = order.Field("SalesOrderID"),
OrderDate = order.Field("OrderDate"),
SalesOrderNumber = order.Field("SalesOrderNumber")
};

foreach (var onlineOrder in query)
{
Console.WriteLine("Order ID: {0} Order date: {1:d} Order number: {2}",
onlineOrder.SalesOrderID,
onlineOrder.OrderDate,
onlineOrder.SalesOrderNumber);
}

________________________________
101 Visual Basic LINQ Samples! - http://msdn.microsoft.com/en-us/vbasic/bb688088.aspx

.NET Framework 3.5 - LINQ to XML

LINQ to XML provê uma interface de programação [in-memory] com [Modelo de Objeto de Documentos - DOM].

O interessante deste novo recurso é que ficou mais fácil a manipulação de XMLs (comparando por exemplo ao uso do XPATH) pois este tipo de objeto é manipulado através de Queries, similar a manipulação no SQL.

O exemplo abaixo mostra a facilidade de uso. Está sendo retornada uma lista, ordernado pelo campo partnumber, dos itens com valor maior do que 100.


[C#]
IEnumerable partNos =
from item in purchaseOrder.Descendants("Item")
where (int) item.Element("Quantity") *
(decimal) item.Element("USPrice") > 100
orderby (string)item.Element("PartNumber")
select item;


[Visual Basic]
Dim partNos = _
From item In purchaseOrder... _
Where (item..Value * _
item..Value) > 100 _
Order By item..Value _
Select item


A melhoria na manipulação da interface de programação XML é realmente significativa. Inclusive podemos fazer todo tipo de manipulação, como fariamos em uma programação XML, sendo elas:

- Carregar XML de arquivos ou streams.
- Serializar XML para arquivos ou streams.
- Query XML usando [XPath-like].
- Manipular a [árvore XML] in-memory usando métodos comm Add, Remove, ReplaceWith, e SetValue.
- Validar a [árvore XML] usando XSD.

Para exemplificar a facilidade na criação da árvore em XML, note o código abaixo:


[C#]
XElement contacts =
new XElement("Contacts",
new XElement("Contact",
new XElement("Name", "Patrick Hines"),
new XElement("Phone", "206-555-0144",
new XAttribute("Type", "Home")),
new XElement("phone", "425-555-0145",
new XAttribute("Type", "Work")),
new XElement("Address",
new XElement("Street1", "123 Main St"),
new XElement("City", "Mercer Island"),
new XElement("State", "WA"),
new XElement("Postal", "68042")
)
)
);


E no Visual Basic o uso é ainda mais simplificado, pois usa-se o XML de forma literal:


[Visual Basic]
Dim contacts = _
< Contacts >
< Contact >
< Name >Patrick Hines< /Name >
< Phone Type="Home" >206-555-0144< /Phone >
< Phone Type="Work">425-555-0145< /Phone >
< Address >
< Street1 >123 Main St< /Street1 >
< City> Mercer Island< /City >
< State >WA< /State >
< Postal >68042< /Postal >
< /Address >
< /Contact >
< /Contacts >


________________________________
101 Visual Basic LINQ Samples! - http://msdn.microsoft.com/en-us/vbasic/bb688088.aspx

.NET Framework 3.5 - LINQ to SQL

LINQ to SQL é um componente do .NET Framework 3.5 que provê uma Infraestrutura em tempo de execução para gerenciar dados relacionais como [objetos].

Por exemplo, o objeto [nw], no código abaixo, é criado para representar o Banco de Dados [Northwind] que faz um "Select" na Tabela [Customers], retornando os registros da cidade de [Londres].


[Visual Basic]
' NorthwndDataContext herda de System.Data.Linq.DataContext.
Dim nw As New NorthwndDataContext("C:\SQL Server 2000 Sample Databases\northwnd.mdf")

Dim companyNameQuery = _
From cust In nw.Customers _
Where cust.City = "London" _
Select cust.CompanyName

For Each customer In companyNameQuery
Console.WriteLine(customer)
Next


[C#]
// NorthwndDataContext herda de System.Data.Linq.DataContext.
NorthwndDataContext nw = new NorthwndDataContext(@"C:\SQL Server 2000 Sample Databases\northwnd.mdf");

var companyNameQuery =
from cust in nw.Customers
where cust.City == "London"
select cust.CompanyName;

foreach (var customer in companyNameQuery)
{
Console.WriteLine(customer);
}


Adicionando [LINQ to SQL Data Classes] em um Projeto

Para trabalharmos de forma orientada a objetos com o LINQ to SQL, como o exemplo acima mostrou no uso da classe [NorthwndDataContext], o Visual Studio 2008 disponibiliza duas ferramentas de apoio para a geração da classes de dados:

- Object Relational Designer (http://msdn.microsoft.com/en-us/library/bb384429.aspx);

- SQLMetal (http://msdn.microsoft.com/pt-br/library/bb386987.aspx);

Com o O/R Designer, temos a disposição uma rica interface, dentro do Visual Studio 2008, para criarmos nossas classes de dados. Repare que existe um [Template] para isto no [VS 2008], chamado [LINQ to SQL Classes], quando se adiciona um item novo no projeto (.dbml).

Com o SQLMetal, fazemos o mesmo, só que fora do Visual Studio 2008, na linha de comando do DOS.

O seguinte exemplo gera a classe de dados (em Visual Basic) baseado no Banco de Dados Northwind:


sqlmetal /code:northwind.vb /language:vb "c:\northwnd.mdf" /sprocs /functions


P.S.: Note que nos exemplos trabalhamos com o Banco de Dados [Northwind] do Sql Server 2000. Ele ainda continua sendo um bom banco para testes (principalmente devido seu tamanho). Para usar este banco no Sql Server 2005, faça o seguinte:

1 - Efetue o download em: http://www.microsoft.com/downloads/details.aspx?familyid=06616212-0356-46a0-8da2-eebc53a68034&displaylang=en

2 - Execute o arquivo [SQL2000SampleDb.msi], e adicione (Attach) o banco [Northwnd.mdf], localizado em [C:\SQL Server 2000 Sample Databases].

________________________________
101 Visual Basic LINQ Samples! - http://msdn.microsoft.com/en-us/vbasic/bb688088.aspx

terça-feira, 10 de fevereiro de 2009

>> CRM 4.0 - Preenchimento Automático de PickList (REVISADO)

------------ REVISÃO ---------------
Efetuei algumas alterações/correções pois, ao salvar o registro, estava gerando erro (explicado no passo 2 abaixo).
------------------------------------

Ainda na atual versão do CRM (4.0) não se pode configurar objetos para preenchimento automático de outros, via configuração básica do produto. Por exemplo, ao selecionar uma Conta, em um [Lookup], retornar em um [Picklist] os Contatos da Conta.

Segue então solução, via desenvolvimento JavaScript, para isto.

1 - Criar um PickList vazio;

2 - Carregar o Picklist com uma quantidade "razoável" de linhas "VAZIAS". Porquê? Acontece que estamos carregando o Picklist dinamicamente. Internamente, o CRM busca em seu [METADADOS] as informações do Picklist tanto no [OnLoad] do Formulário quanto no [OnSave]. Ignorando este processo interno, ao carregar aleatóriamente os dados no PickList vai gerar um erro, pois o CRM nos dirá que [incialmente, ao carregar o formulário, o metadados para este Picklist não continha informação, porém agora contém, gerando uma exceção de índice do Dropdownlist].

3 - No [OnChange] do Lookup desejado (no meu exemplo, de Conta) adicionar o código:

window.getAccountContacts();


4 - E no [OnLoad] do Form do CRM, adicionar o código abaixo:

window.getAccountContacts = function (e)
{
crmForm.all.new_pickcontact.DataValue = null;
crmForm.all.new_pickcontact.Disabled = true;

if (crmForm.all.customerid.DataValue==null) return;

var customerid = crmForm.all.customerid.DataValue[0].id;
var authenticationHeader = GenerateAuthenticationHeader();

var xml = "<" + "?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<" + "soap:Envelope xmlns:soap="+
"\"http://schemas.xmlsoap.org/soap/envelope/\" "+
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "+
"xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">" +
authenticationHeader+
"<" + "soap:Body>" +
"<" + "RetrieveMultiple xmlns="+
"\"http://schemas.microsoft.com/crm/2007/WebServices\">" +
"<" + "query xmlns:q1="+
"\"http://schemas.microsoft.com/crm/2006/Query\" "+
"xsi:type=\"q1:QueryByAttribute\">" +
"<" + "q1:EntityName>contact" + "<" + "/q1:EntityName>" +
"<" + "q1:ColumnSet xsi:type=\"q1:ColumnSet\">" +
"<" + "q1:Attributes>" +
"<" + "q1:Attribute>fullname" + "<" + "/q1:Attribute>" +
"<" + "/q1:Attributes>" +
"<" + "/q1:ColumnSet>" +
"<" + "q1:Attributes>" +
"<" + "q1:Attribute>parentcustomerid" + "<" + "/q1:Attribute>" +
"<" + "/q1:Attributes>" +
"<" + "q1:Values>" +
"<" + "q1:Value xsi:type=\"xsd:string\">"+
customerid+
"<" + "/q1:Value>" +
"<" + "/q1:Values>" +
"<" + "/query>" +
"<" + "/RetrieveMultiple>" +
"<" + "/soap:Body>" +
"<" + "/soap:Envelope>";

xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");
xmlHttpRequest.Open(
"POST",
"/mscrmservices/2007/CrmService.asmx",
true
);

xmlHttpRequest.setRequestHeader(
"SOAPAction",
"http://schemas.microsoft.com/crm/2007/WebServices/RetrieveMultiple" );

xmlHttpRequest.setRequestHeader(
"Content-Type", "text/xml; charset=utf-8" );

xmlHttpRequest.setRequestHeader(
"Content-Length", xml.length );

xmlHttpRequest.onreadystatechange = StateChangeAccountContacts;
xmlHttpRequest.send(xml);
}

function StateChangeAccountContacts()
{
if (xmlHttpRequest.readyState == 4)
{
var resultXml = xmlHttpRequest.responseXML;
var oXmlDoc = new ActiveXObject("Microsoft.XMLDOM");
oXmlDoc.async = false;
oXmlDoc.loadXML(resultXml.xml);
var businessEntities = oXmlDoc.getElementsByTagName('BusinessEntity');

var oTempArray = new Array();
oTempArray[0] = crmForm.all.mit_pickcontact.originalPicklistOptions[0];

for (i=0;i < businessEntities.length;i++)
{
var fullName = businessEntities[i].selectSingleNode('./q1:fullname');
if (fullName != null)
{
var opt = crmForm.all.mit_pickcontact.originalPicklistOptions[i+1];
opt.Text = fullName.text;
oTempArray[i+1] = opt;
}
}

crmForm.all.new_pickcontact.Options = oTempArray;
crmForm.all.new_pickcontact.Disabled = false;
crmForm.all.new_pickcontact.DataValue = crmForm.all.new_pickcontact.originalSelectedIndex;

}
}

crmForm.all.new_pickcontact.originalPicklistOptions = crmForm.all.new_pickcontact.Options;
crmForm.all.new_pickcontact.originalSelectedIndex = crmForm.all.new_pickcontact.selectedIndex;
window.getAccountContacts();

quinta-feira, 5 de fevereiro de 2009

>> CRM 4.0 - Manipulação de Anexos (SDK)

A versão 4.0 do Dynamics CRM simplificou para nós, desenvolvedores, o uso de diversos recursos do SDK. Um deles é a busca e manipulação de Anexos nas entidades do produto.

Para este caso, note que em cada Anotação (relacionada a uma entidade do CRM) existe um campo chamado documentbody que armazena o conteúdo do arquivo anexado pelo usuário (BASE64 Format).

Para demonstrar isto, segue função genérica (C#) que retorna as Anotações (de uma determinada Entidade) que contém anexos. A função retorna o conteúdo de cada anexo (pelo processo de Decoding do .NET).

P.S.: Lembrando que a função consegue ler o conteúdo de anexos no formato do CRM (BASE64). Arquivos com formatos diferentes precisam de tratamentos específicos para leitura. Exemplo, os arquivos do Office2007, como xlsx (http://michaelmalloy.blogspot.com/2008/04/c-read-excel-2007-xlsx-files.html).

Chamada da função que busca as Anotações:

BusinessEntityCollection notes = getEntityNotes(new Guid("272526EA-ED6B-43CC-A8DC-A2965405A463"));

foreach (BusinessEntity be in notes.BusinessEntities)
{
annotation annot = be as annotation;
if (!string.IsNullOrEmpty(annot.documentbody))
{
string attachBody = DecodeByteArryToString(annot.mimetype, annot.documentbody);
}
}



Função que busca as Anotações da Entidade:


public static BusinessEntityCollection getEntityNotes(Guid entityId)
{
// Set up the CRM Service.
CrmAuthenticationToken token = new CrmAuthenticationToken();
token.AuthenticationType = 0;
token.OrganizationName = "OrgName";
CrmService service = new CrmService();
service.Url = "http://localhost:5555/mscrmservices/2007/crmservice.asmx";
service.CrmAuthenticationTokenValue = token;
service.Credentials = System.Net.CredentialCache.DefaultCredentials;

BusinessEntityCollection notes = null;

ConditionExpression caseCondition = new ConditionExpression();
caseCondition.AttributeName = "objectid";
caseCondition.Operator = ConditionOperator.Equal;
caseCondition.Values = new object[] { entityId };

FilterExpression filter = new FilterExpression();
filter.FilterOperator = LogicalOperator.And;
filter.Conditions = new ConditionExpression[] { caseCondition };

QueryExpression query = new QueryExpression();
query.EntityName = EntityName.annotation.ToString();
query.ColumnSet = new AllColumns();
query.Criteria = filter;

RetrieveMultipleRequest retrieve = new RetrieveMultipleRequest();
retrieve.Query = query;

RetrieveMultipleResponse retrieved = (RetrieveMultipleResponse)service.Execute(retrieve);
notes = retrieved.BusinessEntityCollection;

return notes;
}

public static string DecodeByteArryToString(string mimeType, string documentbody)
{
Byte[] ByteArry = System.Convert.FromBase64String(documentbody);
Decoder byteArryDecoder = null;

if (mimeType.IndexOf("text/") >= 0)
{
byteArryDecoder = Encoding.UTF7.GetDecoder();
}
else
{
byteArryDecoder = Encoding.Unicode.GetDecoder();
}

int charCount = byteArryDecoder.GetCharCount(ByteArry, 0, ByteArry.Length);
char[] bodyChars = new Char[charCount];
int charsDecodedCount = byteArryDecoder.GetChars(ByteArry, 0, ByteArry.Length, bodyChars, 0);
return new string(bodyChars);
}

quarta-feira, 4 de fevereiro de 2009

>> CRM 4.0 - PickList Multi-Value

O objeto Picklist do CRM é um recurso interessante para montagem de listas rápidas, porém, dependendo do projeto em que está, vai ser necessário o uso de alguns recursos adicionais do objeto, como o de [multipla escolha]...Então desenvolvi um método (totalmente em JavaScript) que [transforma] o Picklist original em um [Novo], com múltipla escolha.

Segue design de um Picklist [transformado]:







Como funciona a transformação

1 - Crie normalmente seu Picklist e adicione-o no form do crm;

2 - Crie um atributo Auxiliar (nvarchar,texto,100) para armazenar os valores do Picklist Multi-Value; adicione-o também no form do CRM;

3 - Copie a função abaixo no OnLoad do Form do CRM. A função (createMultiValuePickList) possui os seguintes parâmetros:

pickList - Informar o objeto Picklist original do CRM;
objAux - Informar o objeto criado no passo 2 citado acima.

P.S.1.: O Novo Picklist ficará no lugar do [objAux] e o Picklist Original ficará [oculto] no Form do CRM.

P.S.2.: Como a função [createMultiValuePickList] cria objetos [novos] no Form do CRM e não altera e/ou reutiliza qualquer recurso padrão do produto, este procedimento pode se enquadrar na forma [Suportada] de customização.

Segue chamada da função:


createMultiValuePickList(crmForm.all.new_services, crmForm.all.new_services_aux);


Segue função completa:


function createMultiValuePickList(pickList, objAux)
{
/* Desabilitar o PickList do CRM e o campo aux que guardará os valores do Picklist Multi-Value */
var pickLabel = document.getElementById(pickList.id + "_c");
pickLabel.style.visibility = "hidden";
pickList.style.width = "0px";
objAux.style.width = "0px";

/* Cria a Table principal, que conterá o Picklist e o Botão para habilitar o Picklist */
var oMainTable = document.createElement("TABLE");
oMainTable.style.width = '100%';
oMainTable.setAttribute('table-layout', 'fixed');
/* Adiciona a Table Principal */
objAux.insertAdjacentElement("BeforeBegin", oMainTable);

var oMainTHead = document.createElement("THEAD");
oMainTable.appendChild(oMainTHead);

var input1 = document.createElement('input');
input1.setAttribute('type', 'text');
input1.setAttribute('name', 'input_txt');
input1.setAttribute('id', 'input_txt');
input1.setAttribute('readOnly', 'true');

input1.onclick = function()
{
document.getElementById('divPicklist').style.visibility = 'hidden';
document.getElementById('input_btn').style.backgroundImage = 'url(/_imgs/selectOn.gif)';
}


var input2 = document.createElement('input');
input2.style.width = '18px';
input2.setAttribute('type', 'button');
input2.setAttribute('name', 'input_btn');
input2.setAttribute('id', 'input_btn');
input2.style.backgroundImage = 'url(/_imgs/selectOn.gif)';
input2.style.backgroundColor="#D6E8FF";
input2.style.backgroundPosition="center center";

input2.onclick = function()
{
var div1 = document.getElementById('divPicklist');
div1.style.visibility = (div1.style.visibility=='visible' ? 'hidden' : 'visible');
this.style.backgroundImage = (div1.style.visibility=='hidden' ? 'url(/_imgs/selectOn.gif)' : 'url(/_imgs/ico/16_succeeded.png)');
var txt = document.getElementById('input_txt');
txt.value='';
objAux.value='';

for(i=0; i < pickList.options.length; i++)
{
var chk = document.getElementById('chk_' + String(pickList.options[i].value));
if (chk)
{
if (chk.checked)
{
txt.value+=pickList.options[i].text+',';
objAux.value += pickList.options[i].value+',';
}
}
}

if (txt.value.indexOf(',')>0) txt.value= txt.value.substr(0, txt.value.length-1);
if (objAux.value.indexOf(',')>0) objAux.value= objAux.value.substr(0, objAux.value.length-1);

}

var input3 = document.createElement('input');
input3.style.width = '18px';
input3.setAttribute('type', 'button');
input3.setAttribute('name', 'input_btn2');
input3.setAttribute('id', 'input_btn2');
input3.style.backgroundImage = 'url(/_imgs/ico/16_L_remove.gif)';
input3.style.backgroundColor="#D6E8FF";
input3.style.backgroundPosition="center center";

input3.onclick = function()
{
document.getElementById('divPicklist').style.visibility = 'hidden';
document.getElementById('input_btn').style.backgroundImage = 'url(/_imgs/selectOn.gif)';
document.getElementById('input_txt').value='';
objAux.value='';

for(i=0; i < pickList.options.length; i++)
{
var chk = document.getElementById('chk_' + String(pickList.options[i].value));
if (chk) chk.checked=false;
}

}

var oRow1 = document.createElement("TR");
var oRow2 = document.createElement("TR");

var oDivRow2 = document.createElement("DIV");
oDivRow2.setAttribute('id', 'divPicklist');
oDivRow2.style.position = 'absolute';
oDivRow2.style.height = '180px';
oDivRow2.style.width = '100%';
oDivRow2.style.border = 'solid 1px gray';
oDivRow2.style.backgroundColor = 'white';
oDivRow2.style.overflow = 'scroll';
oDivRow2.style.visibility='hidden';

oMainTHead.appendChild(oRow1);
oMainTHead.appendChild(oRow2);

/* 1a Coluna - TextBox */
var oCell1 = document.createElement("TH");
oCell1.style.width = '90%';
oCell1.appendChild(input1);
oRow1.appendChild(oCell1);

/* 2a Coluna - Botão */
var oCell2 = document.createElement("TH");
oCell2.style.width = '10%';
oCell2.appendChild(input2);
oRow1.appendChild(oCell2);

/* 3a Coluna - Botão */
var oCell3 = document.createElement("TH");
oCell3.style.width = '10%';
oCell3.appendChild(input3);
oRow1.appendChild(oCell3);

/* Cria a Table que terá o conteúdo do Picklist Original */
var oTablePick = document.createElement("TABLE");
oTablePick.setAttribute('width', '100%');
oTablePick.setAttribute('table-layout', 'fixed');

var oTHeadPick = document.createElement("THEAD");
oTablePick.appendChild(oTHeadPick);

oDivRow2.appendChild(oTablePick);

var defaultPick = crmForm.all.mit_services;
for(i=0; i < defaultPick.options.length; i++)
{
oRowPick = document.createElement("TR");
oTHeadPick.appendChild(oRowPick);

/* 1a Coluna - CheckBox */
oCellPick1 = document.createElement("TH");
oCellPick1.setAttribute('width', '10%');

if (defaultPick.options[i].text!="")
{
var chk = document.createElement('input');
chk.style.border = 'none';
chk.type = 'checkbox';
chk.id = 'chk_' + String(defaultPick.options[i].value);
chk.name = 'chk_' + String(defaultPick.options[i].value);
oCellPick1.appendChild(chk);
}

oRowPick.appendChild(oCellPick1);

/* 2a Coluna - Texto do PickList Original */
oCellPick2 = document.createElement("TH");
oCellPick2.setAttribute('width', '90%');
oCellPick2.innerHTML = defaultPick.options[i].text;
oRowPick.appendChild(oCellPick2);
}

/* Adicionar oTablePick na 2a linha da oMainTable */
var oCellPick1_line2 = document.createElement("TH");
oCellPick1_line2.setAttribute('width', '90%');
oCellPick1_line2.appendChild(oDivRow2);
oRow2.appendChild(oCellPick1_line2);

/* 2a coluna em branco */
var oCellPick2_line2 = document.createElement("TH");
oCellPick2_line2.style.width = '10%';
oRow2.appendChild(oCellPick2_line2);

/* 3a coluna em branco */
var oCellPick3_line2 = document.createElement("TH");
oCellPick3_line2.style.width = '10%';
oRow2.appendChild(oCellPick3_line2);

/* DataBind do componente */
if (objAux.value.indexOf(",")>0)
{
var txt_width = String(input1.clientWidth) + 'px';
var aBind = objAux.value.split(",");
for(i=0; i <= aBind.length; i++)
{
if (defaultPick.options[aBind[i]])
{
input1.value += defaultPick.options[aBind[i]].text + ',';
var chk = document.getElementById('chk_' + String(aBind[i]));
if (chk) chk.checked=true;
}
}
if (input1.value.indexOf(',')>0)
{
input1.value= input1.value.substr(0, input1.value.length-1);
input1.style.width = txt_width;
}
}

}

quinta-feira, 29 de janeiro de 2009

>> CRM 4.0 - Relationship via SDK

Já tiveram a necessidade de relacionar duas entidades, porém o CRM não permite da forma que deseja?

Por exemplo, pode-se relacionar no CRM a entidade [QuoteDetail] a outras entidades, porém somente via [N:1] ou [N:N]...mas não via [1:N].

Porque isto é relevante? Imagine que deseja ter, na tela de [Produtos da Cotação], [Despesas] associadas a cada Item. Via Customização Padrão do CRM não vai conseguir fazer isto! Pois não aparece na tela do [Produto da Cotação] o [Menu Vertical] de links de relacionamentos com outras entidades (comum a maioria das entidades).

O que se aprende neste artigo:

- Funcionamento de [Relationships] do CRM;
- Como reaproveitar chamadas de URL do produto;
- Usar o SDK para, programaticamente, adicionar e remover relacionamento entre entidades (classes AssociateEntitiesRequest e DisassociateEntitiesRequest).

A solução:

1 - Criar o relacionamento [N:N], para gerar o link entre as entidades [QuoteDetail] e [qq outra];

2 - Adicionar um [iFrame] do CRM, e na URL, adicionar o seguinte:



É claro que a URL deverá ser preenchida programaticamente, via [OnLoad] do Form do CRM, pois notamos na URL, parâmetros como [oId] que vai representar o [Guid] da entidade que [contém] o iFrame, o parâmetro [oType], representando o código da entidade, e por fim, [tabSet] que representa o nome do relacionamento entre as entidades.

No meu exemplo acima estou mostrando no iFrame a [Lista de Despesas] associada a um [Produto da Cotação].

Desta forma, consigo mostrar o relacionamento como se fosse [1:N].

Porém, o trabalho não acabou! Se notar, no cabeçalho do Grid que está sendo mostrado no iFrame, possui apenas o botão [Adicionar Existentes]...o que é inviável, pois queremos simular o processo [1:N], na qual apareceria o botão [Novo ].

Para resolver isto, crie um novo botão customizado, alterando o Isv.Config, no meu caso, para a entidade [Despesa], adicionando um JavaScript que:

1 - Captura o Id da entidade principal (no meu exemplo, o Produto da Cotação);
2 - Criar um atributo para guardar o valor do ID do passo 1. No meu exemplo eu guardo o ID do [Produto da Cotação] em um campo criado na Entidade [Despesas];
3 - Prepare a url que simule a criação do registro;
4 - No [OnLoad] do Form (no meu exemplo, de Despesas), recupere o Id capturado no passo 1.

Abaixo segue códigos para os passos citados:

- Adicionando o botão no Isv.Config do CRM4:



- No [OnLoad] do Form do CRM, JavaScript do passo 3:



E finalmente, criar o código SDK (criar uma dll de PLUGIN - PostCreate e PostDelete) para criar o Link entre as entidades (no meu exemplo, entre QuoteDetail e [outra entidade]):

private static void fnEntityRelationShip(Boolean add)
{
// Set up the CRM Service.
CrmAuthenticationToken token = new CrmAuthenticationToken();
token.AuthenticationType = 0;
token.OrganizationName = "OrgName";

CrmService service = new CrmService();
service.Url = "http://localhost:5555/mscrmservices/2007/crmservice.asmx";
service.CrmAuthenticationTokenValue = token;
service.Credentials = System.Net.CredentialCache.DefaultCredentials;

if (add)
{
// Create an AssociateEntities request.
AssociateEntitiesRequest request = new AssociateEntitiesRequest();

request.Moniker1 = new Moniker();
request.Moniker1.Id = new Guid("a1db8742-a3ec-dd11-a193-0015f2044350");
request.Moniker1.Name = EntityName.quotedetail.ToString();

request.Moniker2 = new Moniker();
request.Moniker2.Id = new Guid("21CA2C7C-7989-DD11-B2DA-0015F2044350");
request.Moniker2.Name = EntityName.mit_despesa.ToString();

// Set the relationship name to associate on.
request.RelationshipName = "mit_quotedetail_mit_despesa";

// Execute the request.
service.Execute(request);
}
else
{
// Create a request.
DisassociateEntitiesRequest request = new DisassociateEntitiesRequest();

// Assign the request a moniker for both entities that need to be disassociated.
request.Moniker1 = new Moniker();
request.Moniker1.Id = new Guid("a1db8742-a3ec-dd11-a193-0015f2044350");
request.Moniker1.Name = EntityName.quotedetail.ToString();

request.Moniker2 = new Moniker();
request.Moniker2.Id = new Guid("21CA2C7C-7989-DD11-B2DA-0015F2044350");
request.Moniker2.Name = EntityName.mit_despesa.ToString();

// Set the relationship name that associates the two entities.
request.RelationshipName = "mit_quotedetail_mit_despesa";

// Execute the request.
DisassociateEntitiesResponse response = (DisassociateEntitiesResponse)service.Execute(request);
}
}