Introducción
En este artículo, exploraremos paso a paso cómo conectarnos a Dynamics 365 utilizando C#, cómo filtrar y recuperar información de las entidades, y cómo guardar estos datos en una base de datos local de SQL Server. Este tutorial está diseñado como si fuera una clase universitaria, proporcionando ejemplos prácticos y explicaciones detalladas para facilitar su comprensión.
Tabla de Contenidos
- 4.1 Configuración de Entity Framework Core
- 4.2 Creación de Modelos de Entidad
- 4.3 Contexto de la Base de Datos
- 4.4 Migraciones y Actualización de la Base de Datos
1. Configuración del Entorno
Antes de comenzar, asegúrate de tener lo siguiente instalado:
- Visual Studio 2022 o superior.
- .NET 8.0 SDK.
- SQL Server (puede ser la versión Express o Developer).
- NuGet Packages necesarios:
Microsoft.PowerPlatform.Dataverse.Client
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
2. Conexión a Dynamics 365
Para interactuar con Dynamics 365, utilizaremos el paquete Microsoft.PowerPlatform.Dataverse.Client
. A continuación, se muestra cómo establecer una conexión.
using Microsoft.PowerPlatform.Dataverse.Client;
// ...
string connectionString = $@"
AuthType=OAuth;
Username=TU_USUARIO;
Password=TU_CONTRASEÑA;
Url=https://TU_ORG.crm.dynamics.com;
AppId=TU_APP_ID;
RedirectUri=TU_REDIRECT_URI;
LoginPrompt=Auto;
";
using (ServiceClient serviceClient = new ServiceClient(connectionString))
{
if (serviceClient.IsReady)
{
Console.WriteLine("¡Conectado a Dynamics 365!");
}
else
{
Console.WriteLine("No se pudo conectar a Dynamics 365.");
Console.WriteLine(serviceClient.LastError);
}
}
Nota: Reemplaza TU_USUARIO
, TU_CONTRASEÑA
, TU_ORG
, TU_APP_ID
y TU_REDIRECT_URI
con tus credenciales y detalles de la aplicación.
3. Recuperación y Filtrado de Datos
3.1 Uso de QueryExpression
QueryExpression
es una clase que permite construir consultas para recuperar datos de Dynamics 365.
using Microsoft.Xrm.Sdk.Query;
// ...
// Definir las columnas a recuperar
ColumnSet columns = new ColumnSet(new string[]
{
"accountid",
"name",
"accountnumber",
"telephone1",
"emailaddress1",
// Agrega más campos según sea necesario
});
// Crear la consulta
QueryExpression query = new QueryExpression("account")
{
ColumnSet = columns
};
3.2 Aplicación de Condiciones y Filtros
Puedes filtrar los datos utilizando condiciones.
// Agregar condiciones a la consulta
query.Criteria = new FilterExpression
{
Conditions =
{
new ConditionExpression("statecode", ConditionOperator.Equal, 0), // Cuentas activas
new ConditionExpression("createdon", ConditionOperator.OnOrAfter, DateTime.UtcNow.AddDays(-7)) // Últimos 7 días
}
};
Para excluir ciertas entidades basadas en una lista de GUIDs:
List<Guid> excludedAccountIds = new List<Guid>
{
new Guid("11111111-1111-1111-1111-111111111111"),
new Guid("22222222-2222-2222-2222-222222222222"),
// Agrega más GUIDs si es necesario
};
if (excludedAccountIds.Any())
{
query.Criteria.AddCondition("accountid", ConditionOperator.NotIn, excludedAccountIds.ToArray());
}
4. Guardar Datos en una Base de Datos Local
4.1 Configuración de Entity Framework Core
Instala los paquetes necesarios:
Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Tools
4.2 Creación de Modelos de Entidad
Crea clases que representen las tablas en tu base de datos.
// Models/AccountEntity.cs
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace YourNamespace.Models
{
public class AccountEntity
{
[Key]
public Guid AccountId { get; set; }
public string Name { get; set; }
public string AccountNumber { get; set; }
public string Telephone1 { get; set; }
public string EmailAddress1 { get; set; }
// Agrega más propiedades según sea necesario
[Column(TypeName = "decimal(18, 2)")]
public decimal? Revenue { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
}
}
Nota: Usa [Column(TypeName = "decimal(18, 2)")]
para evitar problemas de truncamiento con campos decimal
.
4.3 Contexto de la Base de Datos
Define el contexto que manejará la conexión con la base de datos.
// Data/ApplicationDbContext.cs
using Microsoft.EntityFrameworkCore;
using YourNamespace.Models;
namespace YourNamespace.Data
{
public class ApplicationDbContext : DbContext
{
public DbSet<AccountEntity> Accounts { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=TU_SERVIDOR;Database=TU_BASE_DE_DATOS;Trusted_Connection=True;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AccountEntity>(entity =>
{
entity.HasIndex(e => e.CreatedOn).HasDatabaseName("IX_Account_CreatedOn");
entity.HasIndex(e => e.ModifiedOn).HasDatabaseName("IX_Account_ModifiedOn");
});
base.OnModelCreating(modelBuilder);
}
}
}
4.4 Migraciones y Actualización de la Base de Datos
Usa las herramientas de migración de Entity Framework Core para crear y actualizar la base de datos.
Add-Migration InitialCreate
Update-Database
5. Ejemplos de Código Completo
5.1 Recuperar y Guardar Cuentas
// Program.cs
using System;
using System.Collections.Generic;
using Microsoft.PowerPlatform.Dataverse.Client;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using YourNamespace.Data;
using YourNamespace.Models;
namespace YourNamespace
{
class Program
{
static void Main(string[] args)
{
string connectionString = $@"
AuthType=OAuth;
Username=TU_USUARIO;
Password=TU_CONTRASEÑA;
Url=https://TU_ORG.crm.dynamics.com;
AppId=TU_APP_ID;
RedirectUri=TU_REDIRECT_URI;
LoginPrompt=Auto;
";
using (ServiceClient serviceClient = new ServiceClient(connectionString))
{
if (serviceClient.IsReady)
{
Console.WriteLine("¡Conectado a Dynamics 365!");
List<AccountEntity> accountsToSave = RetrieveAccounts(serviceClient);
SaveAccountsToDatabase(accountsToSave);
}
else
{
Console.WriteLine("No se pudo conectar a Dynamics 365.");
Console.WriteLine(serviceClient.LastError);
}
}
Console.WriteLine("Proceso completado. Presiona cualquier tecla para salir.");
Console.ReadKey();
}
static List<AccountEntity> RetrieveAccounts(ServiceClient serviceClient)
{
ColumnSet columns = new ColumnSet(new string[]
{
"accountid",
"name",
"accountnumber",
"telephone1",
"emailaddress1",
"revenue",
"createdon",
"modifiedon"
});
QueryExpression query = new QueryExpression("account")
{
ColumnSet = columns,
Criteria = new FilterExpression
{
Conditions =
{
new ConditionExpression("statecode", ConditionOperator.Equal, 0),
new ConditionExpression("createdon", ConditionOperator.OnOrAfter, DateTime.UtcNow.AddDays(-7))
}
}
};
EntityCollection accounts = serviceClient.RetrieveMultiple(query);
List<AccountEntity> accountEntities = new List<AccountEntity>();
foreach (var account in accounts.Entities)
{
var accountEntity = new AccountEntity
{
AccountId = account.Id,
Name = account.GetAttributeValue<string>("name"),
AccountNumber = account.GetAttributeValue<string>("accountnumber"),
Telephone1 = account.GetAttributeValue<string>("telephone1"),
EmailAddress1 = account.GetAttributeValue<string>("emailaddress1"),
Revenue = account.GetAttributeValue<Money>("revenue")?.Value,
CreatedOn = account.GetAttributeValue<DateTime?>("createdon"),
ModifiedOn = account.GetAttributeValue<DateTime?>("modifiedon")
};
accountEntities.Add(accountEntity);
}
return accountEntities;
}
static void SaveAccountsToDatabase(List<AccountEntity> accounts)
{
using (var context = new ApplicationDbContext())
{
foreach (var account in accounts)
{
var existingAccount = context.Accounts.Find(account.AccountId);
if (existingAccount == null)
{
context.Accounts.Add(account);
}
else
{
context.Entry(existingAccount).CurrentValues.SetValues(account);
}
}
context.SaveChanges();
Console.WriteLine($"Se guardaron {accounts.Count} cuentas en la base de datos.");
}
}
}
}
5.2 Recuperar y Guardar Incidentes
// Models/IncidentEntity.cs
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace YourNamespace.Models
{
public class IncidentEntity
{
[Key]
public Guid IncidentId { get; set; }
public string Title { get; set; }
public string TicketNumber { get; set; }
public string Description { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public Guid? CustomerId { get; set; }
public string CustomerName { get; set; }
public string CustomerType { get; set; }
public int? PriorityCode { get; set; }
public string PriorityLabel { get; set; }
// Agrega más propiedades según sea necesario
}
}
// Actualiza el ApplicationDbContext
public DbSet<IncidentEntity> Incidents { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Configuración existente...
modelBuilder.Entity<IncidentEntity>(entity =>
{
entity.HasIndex(e => e.CreatedOn).HasDatabaseName("IX_Incident_CreatedOn");
entity.HasIndex(e => e.ModifiedOn).HasDatabaseName("IX_Incident_ModifiedOn");
});
base.OnModelCreating(modelBuilder);
}
// Agrega métodos para recuperar y guardar incidentes en Program.cs
static List<IncidentEntity> RetrieveIncidents(ServiceClient serviceClient)
{
ColumnSet columns = new ColumnSet(new string[]
{
"incidentid",
"title",
"ticketnumber",
"description",
"createdon",
"modifiedon",
"customerid",
"prioritycode"
// Agrega más campos según sea necesario
});
QueryExpression query = new QueryExpression("incident")
{
ColumnSet = columns,
Criteria = new FilterExpression
{
Conditions =
{
new ConditionExpression("statecode", ConditionOperator.Equal, 0),
new ConditionExpression("createdon", ConditionOperator.OnOrAfter, DateTime.UtcNow.AddDays(-7))
}
}
};
EntityCollection incidents = serviceClient.RetrieveMultiple(query);
List<IncidentEntity> incidentEntities = new List<IncidentEntity>();
foreach (var incident in incidents.Entities)
{
var incidentEntity = new IncidentEntity
{
IncidentId = incident.Id,
Title = incident.GetAttributeValue<string>("title"),
TicketNumber = incident.GetAttributeValue<string>("ticketnumber"),
Description = incident.GetAttributeValue<string>("description"),
CreatedOn = incident.GetAttributeValue<DateTime?>("createdon"),
ModifiedOn = incident.GetAttributeValue<DateTime?>("modifiedon"),
CustomerId = incident.GetAttributeValue<EntityReference>("customerid")?.Id,
CustomerName = incident.GetAttributeValue<EntityReference>("customerid")?.Name,
CustomerType = incident.GetAttributeValue<EntityReference>("customerid")?.LogicalName,
PriorityCode = incident.GetAttributeValue<OptionSetValue>("prioritycode")?.Value,
PriorityLabel = GetOptionSetLabel(serviceClient, "incident", "prioritycode", incident.GetAttributeValue<OptionSetValue>("prioritycode")?.Value)
};
incidentEntities.Add(incidentEntity);
}
return incidentEntities;
}
static void SaveIncidentsToDatabase(List<IncidentEntity> incidents)
{
using (var context = new ApplicationDbContext())
{
foreach (var incident in incidents)
{
var existingIncident = context.Incidents.Find(incident.IncidentId);
if (existingIncident == null)
{
context.Incidents.Add(incident);
}
else
{
context.Entry(existingIncident).CurrentValues.SetValues(incident);
}
}
context.SaveChanges();
Console.WriteLine($"Se guardaron {incidents.Count} incidentes en la base de datos.");
}
}
// Método auxiliar para obtener etiquetas de OptionSet
static string GetOptionSetLabel(IOrganizationService service, string entityName, string attributeName, int? optionSetValue)
{
if (optionSetValue == null)
return null;
var request = new RetrieveAttributeRequest
{
EntityLogicalName = entityName,
LogicalName = attributeName,
RetrieveAsIfPublished = true
};
var response = (RetrieveAttributeResponse)service.Execute(request);
var metadata = (EnumAttributeMetadata)response.AttributeMetadata;
var option = metadata.OptionSet.Options.FirstOrDefault(o => o.Value == optionSetValue);
return option?.Label.UserLocalizedLabel.Label;
}
6. Conclusión
En este artículo, hemos explorado cómo conectarnos a Dynamics 365 utilizando C#, cómo filtrar y recuperar datos de entidades específicas, y cómo guardar esa información en una base de datos local utilizando Entity Framework Core. Este proceso es esencial para desarrollar aplicaciones que interactúan con Dynamics 365 y requieren almacenamiento local para análisis, informes o integración con otros sistemas.
Al seguir estos pasos y adaptar el código proporcionado a tus necesidades específicas, puedes construir aplicaciones robustas y eficientes que aprovechen al máximo las capacidades de Dynamics 365 y las bases de datos locales.
Recuerda siempre:
- Manejar adecuadamente las excepciones y errores.
- Asegurar que las credenciales y cadenas de conexión se gestionen de forma segura.
- Mantener actualizados los paquetes NuGet y las herramientas utilizadas.