API, Dataverse and extra Database
1.1 Installing the SDK
Microsoft.PowerPlatform.Dataverse.Client
1.2 Setting Up the Dataverse Repository with SDK
The Dataverse SDK offers a ServiceClient class, which simplifies CRUD operations against the Dataverse.
using Microsoft.PowerPlatform.Dataverse.Client;
using YourApiService.Models;
using System.Threading.Tasks;
public class DataverseRepository<T> : IGenericRepository<T> where T : class
{
private readonly ServiceClient _serviceClient;
public DataverseRepository(ServiceClient serviceClient)
{
_serviceClient = serviceClient;
}
public async Task<T> CreateAsync(T entity)
{
var entityId = _serviceClient.Create(entity);
if (entityId != Guid.Empty)
{
return entity; // Return the created entity
}
throw new Exception("Failed to create entity in Dataverse");
}
public async Task<T> GetByIdAsync(Guid id)
{
var entity = _serviceClient.Retrieve(typeof(T).Name.ToLower(), id, new ColumnSet(true)); // Fetch entity with all columns
return entity as T;
}
public async Task<IEnumerable<T>> GetAllAsync()
{
var query = new QueryExpression(typeof(T).Name.ToLower())
{
ColumnSet = new ColumnSet(true)
};
var entities = _serviceClient.RetrieveMultiple(query);
return entities.Entities.Cast<T>().ToList();
}
public async Task<T> UpdateAsync(Guid id, T entity)
{
_serviceClient.Update(entity);
return entity; // Return updated entity
}
public async Task<bool> DeleteAsync(Guid id)
{
_serviceClient.Delete(typeof(T).Name.ToLower(), id);
return true;
}
}
Here’s what’s happening:
- The ServiceClient is used to interact with Dataverse.
- The CreateAsync, GetByIdAsync, GetAllAsync, UpdateAsync, and DeleteAsync methods handle CRUD operations using the SDK.
- This repository is generic and works for any entity type.
1.3 Configuring the SDK in Program.cs
You need to configure the ServiceClient for Dataverse in the Program.cs file. This can be done by using connection strings for Dataverse from your configuration.
builder.Services.AddSingleton<ServiceClient>(sp =>
{
var config = sp.GetRequiredService<IConfiguration>();
var connectionString = config.GetConnectionString("DataverseConnection");
return new ServiceClient(connectionString);
});
{
"ConnectionStrings": {
"DataverseConnection": "AuthType=ClientSecret;Url=https://your-dataverse-url.crm.dynamics.com;ClientId=your-client-id;ClientSecret=your-client-secret;TenantId=your-tenant-id;"
}
}
2. Handling Transaction Consistency Between Dataverse and Java DB
When you perform operations on both Dataverse and the Java database, you need to ensure consistency. If an operation fails in Dataverse, the Java database operation should roll back if it has already been performed.
To handle this:
- Perform the operations in a two-phase commit fashion.
- Use a try-catch mechanism, and only commit the Java DB changes if the Dataverse operation is successful.
2.1 Modifying the Service Layer for Transaction Consistency
Here’s how to update the EntityService class to ensure that if an operation fails on Dataverse, it rolls back changes to the Java database:
public class EntityService : IEntityService
{
private readonly IGenericRepository<Entity> _dataverseRepository;
private readonly IGenericRepository<Entity> _javaDbRepository;
private readonly ApplicationDbContext _dbContext;
public EntityService(IGenericRepository<Entity> dataverseRepository, IGenericRepository<Entity> javaDbRepository, ApplicationDbContext dbContext)
{
_dataverseRepository = dataverseRepository;
_javaDbRepository = javaDbRepository;
_dbContext = dbContext;
}
public async Task<Entity> CreateAsync(EntityDto entityDto)
{
using var transaction = await _dbContext.Database.BeginTransactionAsync();
try
{
// 1. Create entity in Dataverse
var entity = new Entity { Name = entityDto.Name, ... }; // Map DTO to Entity
var dataverseEntity = await _dataverseRepository.CreateAsync(entity);
// 2. Sync the created entity to Java DB
var javaEntity = await _javaDbRepository.CreateAsync(dataverseEntity);
// 3. Commit transaction if both operations are successful
await transaction.CommitAsync();
return javaEntity;
}
catch (Exception ex)
{
// 4. Roll back the Java DB transaction if Dataverse operation fails
await transaction.RollbackAsync();
throw new Exception($"Transaction failed: {ex.Message}");
}
}
public async Task<Entity> UpdateAsync(Guid id, EntityDto entityDto)
{
using var transaction = await _dbContext.Database.BeginTransactionAsync();
try
{
// 1. Update entity in Dataverse
var updatedEntity = new Entity { Id = id, Name = entityDto.Name, ... }; // Map DTO to Entity
var updatedDataverseEntity = await _dataverseRepository.UpdateAsync(id, updatedEntity);
// 2. Sync the updated entity to Java DB
var updatedJavaEntity = await _javaDbRepository.UpdateAsync(id, updatedDataverseEntity);
// 3. Commit transaction if both operations are successful
await transaction.CommitAsync();
return updatedJavaEntity;
}
catch (Exception ex)
{
// 4. Roll back the Java DB transaction if Dataverse operation fails
await transaction.RollbackAsync();
throw new Exception($"Transaction failed: {ex.Message}");
}
}
public async Task<bool> DeleteAsync(Guid id)
{
using var transaction = await _dbContext.Database.BeginTransactionAsync();
try
{
// 1. Delete entity in Dataverse
var dataverseResult = await _dataverseRepository.DeleteAsync(id);
// 2. Delete entity in Java DB if Dataverse deletion succeeds
var javaResult = await _javaDbRepository.DeleteAsync(id);
// 3. Commit transaction if both operations are successful
await transaction.CommitAsync();
return dataverseResult && javaResult;
}
catch (Exception ex)
{
// 4. Roll back the Java DB transaction if Dataverse operation fails
await transaction.RollbackAsync();
throw new Exception($"Transaction failed: {ex.Message}");
}
}
}
In this setup:
- The transaction begins only for Java DB (since Dataverse doesn't support traditional database transactions).
- Dataverse operations are performed first.
- If Dataverse fails, the Java DB transaction is rolled back.
3. Generic Repository Approach for Both Dataverse and Java DB
Using a generic repository helps maintain uniformity and reusability across different entities and data sources (Dataverse and Java DB). You’ve already seen how the DataverseRepository uses generics, and the same applies to the JavaDbRepository.
3.1 Generic Repository Interface
Here’s a common interface you can use for both Dataverse and Java DB repositories:
public interface IGenericRepository<T> where T : class
{
Task<T> CreateAsync(T entity);
Task<T> GetByIdAsync(Guid id);
Task<IEnumerable<T>> GetAllAsync();
Task<T> UpdateAsync(Guid id, T entity);
Task<bool> DeleteAsync(Guid id);
}
3.2 Generic Dataverse Repository (Updated to Use SDK)
public class DataverseRepository<T> : IGenericRepository<T> where T : class
{
private readonly ServiceClient _serviceClient;
public DataverseRepository(ServiceClient serviceClient)
{
_serviceClient = serviceClient;
}
public async Task<T> CreateAsync(T entity)
{
var entityId = _serviceClient.Create(entity);
return entityId != Guid.Empty ? entity : throw new Exception("Failed to create entity in Dataverse");
}
public async Task<T> GetByIdAsync(Guid id)
{
var entity = _serviceClient.Retrieve(typeof(T).Name.ToLower(), id, new ColumnSet(true));
return entity as T;
}
public async Task<IEnumerable<T>> GetAllAsync()
{
var query = new QueryExpression(typeof(T).Name.ToLower())
{
ColumnSet = new ColumnSet(true)
};
var entities = _serviceClient.RetrieveMultiple(query);
return entities.Entities.Cast<T>().ToList();
}
public async Task<T> UpdateAsync(Guid id, T entity)
{
_serviceClient.Update(entity);
return entity;
}
public async Task<bool> DeleteAsync(Guid id)
{
_serviceClient.Delete(typeof(T).Name.ToLower(), id);
return true;
}
}
The JavaRepository or any other Additional repository follow the regular generic pattern.
Summary of the Adjustments
- Dataverse SDK is integrated using the ServiceClient to interact with Dataverse.
- Transaction consistency between Dataverse and Java DB is ensured with a try-catch mechanism and transaction rollbacks.
- A generic repository pattern is applied for both Dataverse and Java DB, making it easier to maintain uniform operations and shared models between the two databases.
This approach provides scalability and maintainability, especially if you need to add more data sources or services in the future.
No files yet, migration hasn't completed yet!