EF Core Adapter is the EF Core adapter for Casbin. With this library, Casbin can load policy from EF Core supported database or save policy to it.
The current version supported all databases which EF Core supported, there is a part list:
- SQL Server 2012 onwards
- SQLite 3.7 onwards
- Azure Cosmos DB SQL API
- PostgreSQL
- MySQL, MariaDB
- Oracle DB
- Db2, Informix
- And more...
You can see all the list at Database Providers.
dotnet add package Casbin.NET.Adapter.EFCore
The adapter supports the following .NET target frameworks:
- .NET 9.0
- .NET 8.0
- .NET 7.0
- .NET 6.0
- .NET 5.0
- .NET Core 3.1
using Casbin.Adapter.EFCore;
using Microsoft.EntityFrameworkCore;
using NetCasbin;
namespace ConsoleAppExample
{
public class Program
{
public static void Main(string[] args)
{
// You should build a DbContextOptions for CasbinDbContext<TKey>.
// The example use the SQLite database named "casbin_example.sqlite3".
var options = new DbContextOptionsBuilder<CasbinDbContext<int>>()
.UseSqlite("Data Source=casbin_example.sqlite3")
.Options;
var context = new CasbinDbContext<int>(options);
// If it doesn't exist, you can use this to create it automatically.
context.Database.EnsureCreated();
// Initialize a EF Core adapter and use it in a Casbin enforcer:
var efCoreAdapter = new EFCoreAdapter<int>(context);
var e = new Enforcer("examples/rbac_model.conf", efCoreAdapter);
// Load the policy from DB.
e.LoadPolicy();
// Check the permission.
e.Enforce("alice", "data1", "read");
// Modify the policy.
// e.AddPolicy(...)
// e.RemovePolicy(...)
// Save the policy back to DB.
e.SavePolicy();
}
}
}When using the adapter with dependency injection (e.g., in ASP.NET Core), you should use the IServiceProvider constructor or the extension method to avoid issues with disposed DbContext instances.
using Casbin.Persist.Adapter.EFCore;
using Casbin.Persist.Adapter.EFCore.Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
// Register services
services.AddDbContext<CasbinDbContext<int>>(options =>
options.UseSqlServer(connectionString));
// Register the adapter using the extension method
services.AddEFCoreAdapter<int>();
// The adapter will resolve the DbContext from the service provider on each operation,
// preventing issues with disposed contexts when used with long-lived services.// In your startup configuration
services.AddDbContext<CasbinDbContext<int>>(options =>
options.UseSqlServer(connectionString));
services.AddCasbinAuthorization(options =>
{
options.DefaultModelPath = "model.conf";
// Use the IServiceProvider constructor
options.DefaultEnforcerFactory = (sp, model) =>
new Enforcer(model, new EFCoreAdapter<int>(sp));
});This approach resolves the DbContext from the service provider on each database operation, ensuring that:
- The adapter works correctly with scoped DbContext instances
- No
ObjectDisposedExceptionis thrown when the adapter outlives the scope that created it - The adapter can be used in long-lived services like singletons
The adapter supports storing different policy types in separate database contexts, allowing you to:
- Store policies (p, p2, etc.) and groupings (g, g2, etc.) in different schemas and/or tables
- Each Context can control both schema AND table independently
- Separate data for multi-tenant or compliance scenarios
// Create ONE shared connection object
var sharedConnection = new SqlConnection(connectionString);
// Create contexts with shared connection
var policyContext = new CasbinDbContext<int>(
new DbContextOptionsBuilder<CasbinDbContext<int>>()
.UseSqlServer(sharedConnection).Options, // Shared connection
schemaName: "policies");
var groupingContext = new CasbinDbContext<int>(
new DbContextOptionsBuilder<CasbinDbContext<int>>()
.UseSqlServer(sharedConnection).Options, // Same connection
schemaName: "groupings");
// Create a provider that routes policy types to contexts
var provider = new PolicyTypeContextProvider(policyContext, groupingContext);
// Use the provider with the adapter
var adapter = new EFCoreAdapter<int>(provider);
var enforcer = new Enforcer("rbac_model.conf", adapter);
// All operations work transparently across contexts
enforcer.AddPolicy("alice", "data1", "read"); // → policyContext
enforcer.AddGroupingPolicy("alice", "admin"); // → groupingContext
enforcer.SavePolicy(); // Atomic across both
⚠️ Transaction Integrity RequirementsFor atomic multi-context operations:
- Share DbConnection: All contexts must use the same
DbConnectionobject (reference equality)- Disable AutoSave: Use
enforcer.EnableAutoSave(false)and callSavePolicyAsync()to batch commit- Supported databases: PostgreSQL, MySQL, SQL Server, SQLite (same file)
Why disable AutoSave? With
EnableAutoSave(true)(default), each policy operation commits immediately and independently. If a later operation fails, earlier operations remain committed. WithEnableAutoSave(false), all changes stay in memory untilSavePolicyAsync()commits them atomically across all contexts using a shared connection-level transaction.
- ✅ Atomic: Same
DbConnectionobject +EnableAutoSave(false)+SavePolicyAsync()- ❌ Not Atomic: AutoSave ON, separate
DbConnectionobjects, different databasesSee detailed explanation in EnableAutoSave and Transaction Atomicity.
- Multi-Context Usage Guide - Complete step-by-step guide with examples
- Multi-Context Design - Detailed design documentation and limitations
- Integration Tests Setup - How to run transaction integrity tests locally
This project is under Apache 2.0 License. See the LICENSE file for the full license text.