Sunday, 29 November 2020

Dynamics 365 CRM API Limits

Dynamics 365 API limit is a big topic. It was a bit confusing to understand the whole thing. From my understanding, there are two types of API limits. Please refer Microsoft documentation links for accurate info. This post is based on the info that I collected upto November 2020. Micorosoft might change these in the future.

1) Requests limits & allocation 

 This one, your system wouldn't throttle so operations can continue without issues. However there may be cost implications if you exceed the daily API limit. Might need to buy API Limit licenses( new license introduced by Microsoft). It includes most of the operations like plugins, custom workflow activity etc. 

For instance, Dynamics 365 Enterprise application user license has a limit of 20000 API requests per 24 hours

More details are in the below link.

https://docs.microsoft.com/en-us/power-platform/admin/api-request-limits-allocations

2) Service Protection API limits

This one , your system would throttle if you exceed this sliding window limit( 5minutes). So it is necessary that a retry mechanism should be in place ( For both connection types-  organisation service SOAP Api and CRM Web Api)  It excludes plugins, custom workflow activity. It is explained in the below link.

"The service protection API limits are evaluated within a 5 minute (300 second) sliding window. If any of the limits are exceeded within the preceding 300 seconds, a service protection API Limit error will be returned on subsequent requests to protect the service until the Retry-After duration has ended." 


MeasureDescriptionLimit per web server
Number of requests The cumulative number of requests made by the user. 6000 withing 5min sliding window
Execution time The combined execution time of all requests made by the user. 20 minutes (1200 seconds)
Number of concurrent requestsThe number of concurrent requests made by the user52

Ref:https://docs.microsoft.com/en-us/powerapps/developer/common-data-service/api-limits#retry-operations


Saturday, 28 November 2020

Generic Repository Design Pattern with Entity Framework Core

Generic Repository pattern is a design pattern available in C#. Found it very cool!. Generic repository design pattern is very useful when we have more than one entity to deal with in the Entity Framework Core ( EF Core Basics are explained here -https://crmdm.blogspot.com/2020/05/how-to-write-to-sql-table-using-entity.html ). This  design pattern avoids a lot of repetitive code.

For instance, consider two entities used in the entity framework core.

  • Contact
  • Loan

Now these two entites have common operations like inserting to a sql table, read from sql table, removing from the sql table. So we could utilise a generic repository for this. This would avoid repetitive code throughout. Code looks clean too.

In this post I have followed TDD approach. Entity Framework Core unit testing is done using Xunit and Sqlite

 There are 3 options for Entity Framework Core unit testing. Had discussion with my colleague on this specific part as well. It turned out that the sqlite is the better option as other ones have limiations.Hence it is used here. ( ref :https://docs.microsoft.com/en-us/ef/core/testing/ )

First things First. Model is one of our first bricks.

Models: These are a couple of sample models. Feel free to define yours. Please note that Key is a must. Key could be of any data type.

Contact Model.

using System.ComponentModel.DataAnnotations;

namespace RepositoryDesignPattern.Domain
{
    public class Contact
    {
        [Key]
        public int CustomerId { get; set; }

        public string Name { get; set; }

        public string Description { get; set; }
    }
}

Loan Model

using System.ComponentModel.DataAnnotations;

namespace RepositoryDesignPattern.Domain.Model
{
    public class Loan
    {
        [Key]
        public int LoanId { get; set; }

        public decimal Balance { get; set; }

        public string Description { get; set; }

    }
}

Next is our generic interface- IRepository - feel free to name as per your preference. Also more generic methods can be included as per the requirements.

using System.Collections.Generic;

namespace RepositoryDesignPattern.Application.Repositories
{
    public interface IRepository<T> where T : class
    {
        void Add(T entity);

        IEnumerable<T> All();

        void SaveChanges();

        void Remove(T entity);
    }
}

Now the generic repository class which is implemented from the above interface.

using Microsoft.EntityFrameworkCore;
using RepositoryDesignPattern.Application.Repositories;
using System.Collections.Generic;
using System.Linq;

namespace RepositoryDesignPattern.Infrastructure.Repositories
{
    public class Repository<T> : IRepository<T> where T : class
    {
        protected readonly DbContext _context;
        protected readonly DbSet<T> _entities;

        public Repository(DbContext context)
        {
            _context = context;
            _entities = context.Set<T>();
        }

        public void Add(T entity) => _entities.Add(entity);

        public  IEnumerable<T> All() => _context.Set<T>().ToList();

        public  void SaveChanges() => _context.SaveChanges();

        public  void Remove(T entity) => _context.Remove(entity);
    }
}

-----------------------------------------------------------------------------------------------------

Optional Note: This part is useful when the repos get expanded. Not required for demoing generic repository.
It is possible to override these methods from another repository if needed. In that case we need to add virtual keyword for the method in the generic repo.

Also generic repository can be marked as an abstract class at a later stage when other repos are defined properly.

For instance, 

  public virtual void Add(T entity) => _entities.Add(entity);

Overriding from another repo. For instance if we go with a new loan repo,

using RepositoryDesignPattern.Domain.Model;

namespace RepositoryDesignPattern.Infrastructure.Repositories
{
    public class LoanRepository : Repository<Loan>
    {
        public LoanRepository(SqlDbContext dbContext) : base(dbContext)
        {

        }

        public override void Add(Loan loan)
        {
            loan.Description = "Override";
            base.Add(loan);
        }
    }
}

----------------------------------------------------------------------------------------------------

So we are mainly interested in Adding a new record, Retrieving all records from a specific table, Save changes to the actual table, Remove a record. Add or remove operations get completed after applying savechanges. T represents a type parameter in C#. In this scenario it could be Loan or Contact entity.

Next part is to define a custom Dbcontext. This is to handle our own Sql DB and tables. DbContextOptions is an important part. This could be an actual sql db or in memory db or any other supported db by Entity Framework Core. You could see two tables Contact and Loan. DbContextOptions value is passed in the constructor.

using Microsoft.EntityFrameworkCore;
using RepositoryDesignPattern.Domain;
using RepositoryDesignPattern.Domain.Model;

namespace RepositoryDesignPattern.Infrastructure
{
    public class SqlDbContext : DbContext
    {
        public SqlDbContext(DbContextOptions options) : base(options) { }

        public DbSet<Contact> Contacts { get; set; }

        public DbSet<Loan> Loans { get; set; }
    }
}

As per the TDD approach we use the unit tests to provide in memory db and test our code.

Arrange part is set up using Sqlite ( Please note the part .UseSqlite. In a real scenario this part would be .UseSqlServer ). Act part is calling the actual code. And assert part evaluates expected result and output.

using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using RepositoryDesignPattern.Domain.Model;
using RepositoryDesignPattern.Infrastructure;
using RepositoryDesignPattern.Infrastructure.Repositories;
using System.Data.Common;
using System.Linq;
using Xunit;

namespace RepositoryDesignPattern.Unit.Tests
{
    public class SqlRepositoryTests
    {
        [Fact]
        public void Insert_New_LoanRecord_And_Read_Successfully()
        {
            //Arrange
            var dbContextOptions = new DbContextOptionsBuilder<SqlDbContext>()
                .UseSqlite(CreateInMemoryDatabase())
                .Options;
            var context = new SqlDbContext(dbContextOptions);
            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();

            Loan loanRecord = GetSampleLoanRecord();
            var expectedCount = 1;

            //Act
            var repository = new Repository<Loan>(context);
            repository.Add(loanRecord);
            repository.SaveChanges();
            var result = repository.All();

            //Assert
            Assert.Equal(expectedCount, result.Count());
            Assert.Equal(loanRecord.LoanId, result.FirstOrDefault().LoanId);
            Assert.Equal(loanRecord.Description, result.FirstOrDefault().Description);
            Assert.Equal(loanRecord.Balance, result.FirstOrDefault().Balance);
        }

        [Fact]
        public void Insert_New_LoanRecord_And_Removes_Successfully()
        {
            //Arrange
            var dbContextOptions = new DbContextOptionsBuilder<SqlDbContext>()
                .UseSqlite(CreateInMemoryDatabase())
                .Options;
            var context = new SqlDbContext(dbContextOptions);
            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();

            Loan loanRecord = GetSampleLoanRecord();
            var expectedCount = 0;

            //Act
            var repository = new Repository<Loan>(context);
            repository.Add(loanRecord);
            repository.SaveChanges();
            repository.Remove(loanRecord);
            repository.SaveChanges();
            var result = repository.All();

            //Assert
            Assert.Equal(expectedCount, result.Count());
        }

        private DbConnection CreateInMemoryDatabase()
        {
            var connection = new SqliteConnection("Filename=:memory:");
            connection.Open();

            return connection;
        }

        private Loan GetSampleLoanRecord()
        {
            Loan loanRecord = new Loan();
            loanRecord.LoanId = 22;
            loanRecord.Description = "unit test";
            loanRecord.Balance = 1000.00m;

            return loanRecord;
        }
    }
}
 

Green is a good sign : )