Skip to content

feat: Multi-Context Support for EFCore Adapter#93

Merged
hsluoyz merged 11 commits intoapache:masterfrom
verji:taj/multi-dbcontext-v2-pr
Nov 17, 2025
Merged

feat: Multi-Context Support for EFCore Adapter#93
hsluoyz merged 11 commits intoapache:masterfrom
verji:taj/multi-dbcontext-v2-pr

Conversation

@thoraj
Copy link
Copy Markdown
Contributor

@thoraj thoraj commented Nov 13, 2025

Multi-Context Support for EFCore Adapter

Summary

This PR adds multi-context support to the EFCore adapter, enabling different Casbin policy types to be stored in separate database locations (schemas, tables, or databases) while maintaining transactional integrity and full backward compatibility.

Motivation

Organizations often need to separate policy data for various reasons:

  • Multi-tenancy: Isolate tenant data in separate schemas
  • Compliance: Separate sensitive role assignments from general policies
  • Performance: Distribute data across different databases or table partitions

Key Features

1. Multi-Context Architecture

  • ICasbinDbContextProvider<TKey> interface: Routes policy types (p, p2, g, g2, etc.) to appropriate DbContext instances
  • Per-context schema/table control: Each context independently controls both schemaName and tableName
  • Flexible routing: Developers implement routing logic based on their requirements

2. Transaction Integrity

  • Atomic operations: When contexts share a DbConnection, all changes commit atomically or rollback together
  • Shared connection support: PostgreSQL, MySQL, SQL Server, SQLite (same file)
  • EnableAutoSave integration: Disable AutoSave for batched atomic commits across contexts

3. Backward Compatibility

  • Zero breaking changes: Existing single-context usage works unchanged
  • SingleContextProvider<TKey>: Default provider maintains current behavior
  • All existing tests passing: 186 unit tests + 114 integration tests = 300 tests across all frameworks

Implementation Details

New Types

ICasbinDbContextProvider

public interface ICasbinDbContextProvider<TKey> where TKey : IEquatable<TKey>
{
    DbContext GetContextForPolicyType(string policyType);
    IEnumerable<DbContext> GetAllContexts();
    DbConnection? GetSharedConnection();
}

SingleContextProvider (default, backward-compatible)

  • Wraps single DbContext
  • Returns same context for all policy types

Example: Policy/Grouping Separation

// Create shared connection for atomic transactions
var sharedConnection = new SqlConnection(connectionString);

// Create contexts with different schemas
var policyContext = new CasbinDbContext<int>(
    new DbContextOptionsBuilder<CasbinDbContext<int>>()
        .UseSqlServer(sharedConnection).Options,
    schemaName: "policies",
    tableName: "casbin_rule");

var groupingContext = new CasbinDbContext<int>(
    new DbContextOptionsBuilder<CasbinDbContext<int>>()
        .UseSqlServer(sharedConnection).Options,
    schemaName: "groupings",
    tableName: "casbin_rule");

// Implement provider
public class PolicyTypeContextProvider : ICasbinDbContextProvider<int>
{
    public DbContext GetContextForPolicyType(string policyType)
    {
        return policyType.StartsWith("p") ? _policyContext : _groupingContext;
    }

    public DbConnection? GetSharedConnection() => _sharedConnection;
}

// Use with adapter
var adapter = new EFCoreAdapter<int>(provider);
var enforcer = new Enforcer("model.conf", adapter);
enforcer.EnableAutoSave(false);

// All operations work transparently
enforcer.AddPolicy("alice", "data1", "read");      // → policies schema
enforcer.AddGroupingPolicy("alice", "admin");      // → groupings schema
await enforcer.SavePolicyAsync();                  // Atomic commit across both

Testing

Test Coverage: 300 Tests Across 6 Frameworks

Unit Tests (186 total)

  • 31 tests × 6 frameworks (net9.0, net8.0, net7.0, net6.0, net5.0, netcoreapp3.1)
  • BackwardCompatibilityTest: Ensures existing single-context usage unchanged
  • MultiContextTest: Validates policy routing and filtered loading

Integration Tests (114 total)

  • 19 tests × 6 frameworks
  • AutoSaveTests: Verifies EnableAutoSave behavior with atomic transactions
  • SchemaDistributionTests: Confirms policies stored in correct schemas
  • TransactionIntegrityTests: Validates atomic commit/rollback across contexts

Test Results

✅ All 300 tests passing
✅ Zero build warnings
✅ Full backward compatibility verified

Schema Extension

Extended supported schema from v0-v5 (6 columns) to v0-v13 (14 columns) to support Casbin.NET 2.16.0+ models with up to 14 policy values.

Migration Guide

Existing Users (No Changes Required)

// This continues to work unchanged
var context = new CasbinDbContext<int>(options);
var adapter = new EFCoreAdapter<int>(context);

New Multi-Context Users

  1. Create contexts with shared connection
  2. Implement ICasbinDbContextProvider<TKey>
  3. Pass provider to new EFCoreAdapter<int>(provider)
  4. Use EnableAutoSave(false) + SavePolicyAsync() for atomic operations

Documentation

Breaking Changes

None - Full backward compatibility maintained.

Dependencies

  • No new dependencies added
  • Casbin.NET 2.19.1 (already in use)
  • EF Core versions per target framework (unchanged)

Checklist

  • All tests passing (300/300 across all frameworks)
  • Documentation updated (README.md, usage guide, design doc)
  • Backward compatibility verified
  • Integration tests for new functionality
  • Example code provided

This PR implements support for using multiple CasbinDbContext instances
with a single EFCoreAdapter, enabling separation of policy and grouping
data across different databases, schemas, or tables.

Key Features:
- Multi-context architecture via ICasbinDbContextProvider interface
- Transaction support for atomic operations across contexts
- Shared connection pooling when contexts use same connection string
- Full backward compatibility with existing single-context usage
- Extended schema support from v0-v5 to v0-v13 columns

Implementation:
- Add ServiceProviderContextProvider for DI scenarios
- Add SingleContextProvider for direct DbContext usage
- Implement context routing based on policy type (p/g)
- Add shared connection management for atomic transactions
- New CasbinDbContextExtension.Clear() method for testing

Testing:
- 120 tests passing across all target frameworks (.NET 6.0, 8.0, 9.0)
- New integration tests for multi-context scenarios
- Transaction integrity tests for atomic operations
- Schema distribution tests across contexts
- AutoSave behavior tests
- Backward compatibility tests for single-context usage

Documentation:
- MULTI_CONTEXT_DESIGN.md: Architecture and design decisions
- MULTI_CONTEXT_USAGE_GUIDE.md: Usage patterns and examples
- TEST_SUMMARY.md: Comprehensive test coverage documentation
- Updated README.md with multi-context examples

This implementation maintains full API compatibility while enabling
advanced multi-database scenarios for large-scale deployments.
- Create SimpleFieldFilter test helper to replace obsolete Filter class
- Update test files to use SimpleFieldFilter with Policy.ValuesFrom()
- Update documentation with modern IPolicyFilter examples
- Use xunit.runner.visualstudio 2.4.5 for .NET Core 3.1+ support
- Add #nullable enable directives for nullable annotation context

Eliminates all CS0618 warnings from deprecated Filter usage.
All 300 tests passing across 6 target frameworks.
…pport

- Clarify that Context controls BOTH schema AND table independently
- Add "Supported Frameworks" section listing all target frameworks:
  - .NET 9.0, 8.0, 7.0, 6.0, 5.0, and .NET Core 3.1
- Improve multi-context documentation accuracy

Addresses documentation review feedback to ensure accuracy of
implementation capabilities and framework compatibility.
TEST_SUMMARY.md is an internal test documentation file that should not
be part of the public PR. Test coverage details are already documented
in PR_DESCRIPTION.md and the integration test README.
- Move integration tests to dedicated Casbin.Persist.Adapter.EFCore.IntegrationTest project
- Add xunit.runner.json configuration for both test projects
- Remove ServiceProviderContextProvider (consolidated into main implementation)
- Update solution file to reflect new project structure
…ation

Remove all diagnostic Console.WriteLine statements from production adapter code
and test fixtures. Update integration test README to reflect actual tests and
remove historical/evolution content. Update PostgreSQL credentials to postgres4all!.

Changes:
- Remove ~65 diagnostic Console.WriteLine from EFCoreAdapter.cs
- Remove ~10 diagnostic Console.WriteLine from TransactionIntegrityTestFixture.cs
- Fix unused exception variable warnings (catch Exception ex -> catch)
- Update PostgreSQL password to postgres4all! in code and documentation
- Remove "Bug History" section from integration test README
- Fix test counts (11→10 AutoSave tests, 20→19 total tests)
- Remove non-existent test: SavePolicy_WhenDuplicateKeyViolationInOneContext_ShouldRollbackAllContexts
- Fix SchemaDistributionTests test names to match actual code
- Remove non-existent AutoSave test references
- Fix example commands with correct test names
- Remove "Test Evolution" and "Duplicate Key Violation Test" sections
Remove reference to non-existent test SavePolicy_WhenDuplicateKeyViolationInOneContext_ShouldRollbackAllContexts
and update example command to use an actual test name.
@hsluoyz hsluoyz requested a review from sagilio November 13, 2025 13:11
Add --filter "Category!=Integration" to dotnet test commands in both
build.yml and release.yml workflows to prevent integration tests from
running in CI/CD.

Integration tests require PostgreSQL with specific configuration and
should only be run locally. They are properly marked with
[Trait("Category", "Integration")] and can be run locally with:
dotnet test --filter "Category=Integration"

This ensures:
- Only unit tests (186 tests × 6 frameworks) run in CI/CD
- Integration tests (19 tests × 6 frameworks) excluded from CI/CD
- No PostgreSQL dependency in CI/CD pipeline
@thoraj thoraj marked this pull request as ready for review November 13, 2025 13:34
@hsluoyz hsluoyz requested review from Copilot and removed request for sagilio November 14, 2025 16:19
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds comprehensive multi-context support to the EFCore adapter, enabling Casbin policies to be stored across separate database schemas, tables, or databases while maintaining transactional integrity where possible. The implementation introduces a provider pattern for context routing while maintaining full backward compatibility with existing single-context usage.

Key Changes:

  • Introduced ICasbinDbContextProvider<TKey> interface for routing policy types to appropriate DbContext instances
  • Added SingleContextProvider<TKey> for backward compatibility
  • Extended adapter to coordinate transactions across multiple contexts using shared DbConnection objects
  • Added comprehensive documentation and integration tests

Reviewed Changes

Copilot reviewed 33 out of 34 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
ICasbinDbContextProvider.cs New interface for multi-context provider pattern
SingleContextProvider.cs Default single-context provider for backward compatibility
EFCoreAdapter.cs Extended with multi-context constructors and transaction coordination logic
EFCoreAdapter.Internal.cs Refactored internal methods to support context routing
DefaultPersistPolicyEntityTypeConfiguration.cs Extended schema from v0-v5 to v0-v13 (14 columns)
README.md Added multi-context documentation and examples
MULTI_CONTEXT_USAGE_GUIDE.md Comprehensive step-by-step usage guide
MULTI_CONTEXT_DESIGN.md Technical architecture and design decisions
BackwardCompatibilityTest.cs 10 tests verifying existing behavior unchanged
MultiContextTest.cs 14 tests for multi-context functionality
Integration tests 19 tests across 3 test classes verifying transaction integrity
.github/workflows/*.yml Updated to exclude integration tests from CI/CD

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Casbin.Persist.Adapter.EFCore/EFCoreAdapter.cs
Comment thread Casbin.Persist.Adapter.EFCore/EFCoreAdapter.cs
Comment thread Casbin.Persist.Adapter.EFCore/EFCoreAdapter.cs
Comment thread Casbin.Persist.Adapter.EFCore/EFCoreAdapter.cs
Comment thread Casbin.Persist.Adapter.EFCore/EFCoreAdapter.cs
@hsluoyz
Copy link
Copy Markdown
Member

hsluoyz commented Nov 14, 2025

@thoraj fix the review: #93 (review)

Updated CreateEnforcerWithSeparateConnectionsAsync to return tuple
with all disposable resources (connections and contexts) and updated
call site to properly dispose them in finally block. This resolves
resource leaks detected by IDE analysis.
Removed initialPolicyCount and initialGroupingCount variables
from TestMultiContextUpdatePolicyNoException as they were assigned
but never used. Addresses code review feedback.
Updated PersistPolicies property to call the two-parameter version
of GetCasbinRuleDbSet instead of the obsolete one-parameter version.
This maintains virtual method extensibility while eliminating the
CS0618 obsolete method warning.
@thoraj
Copy link
Copy Markdown
Contributor Author

thoraj commented Nov 17, 2025

@hsluoyz I think I handled all review comments now.

This comment was marked as spam.

@hsluoyz hsluoyz changed the title Multi-Context Support for EFCore Adapter feat: Multi-Context Support for EFCore Adapter Nov 17, 2025
@hsluoyz hsluoyz merged commit 1cc2c9a into apache:master Nov 17, 2025
11 checks passed
@github-actions
Copy link
Copy Markdown

🎉 This PR is included in version 2.12.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants