EfCore.SchemaCompare

repository·master·Indexed 41 days ago

https://github.com/jonpsmith/efcore.schemacompare

A library to verify that an EF Core database model is in sync with the actual database schema. It validates tables, views, properties, keys, and indexes, and identifies unused database items. Supports all scaffoldable EF Core database providers in version 8 and above, and SqlServer, Sqlite, and Npgsql.EntityFrameworkCore.PostgreSQL in versions below 8.

Tokens
2.3K
Snippets
6
Records
10
Agent score
1%

What's inside EfCore.SchemaCompare

  1. How EfSchemaCompare works

    master

    EfSchemaCompare validates that your EF Core Model (the schema defined in your code) is in sync with the actual database schema. It uses two stages of verification:

    1. Stage 1 (EF Core vs Database): Checks if the EF Core DbContext matches the database. It verifies:

      • Tables/Views: Existence, name, and schema.
      • Properties/Columns: Existence, data type (size/precision), nullability, computed column settings, and default values.
      • Primary Keys: Constraint names and properties.
      • Foreign Keys: Constraint names, delete behavior, and properties.
      • Indexes: Constraint names, uniqueness, and properties.
    2. Stage 2 (Database Unused Items): Checks the database for items that are not mapped in your EF Core model, specifically:

      • Unused tables or views
      • Unused columns
      • Unused indexes
  2. Install EfCore.SchemaCompare

    master

    To use EfCore.SchemaCompare, install the NuGet package EfCore.SchemaCompare.

    Important for Version 8 and above: You MUST also add the Microsoft.EntityFrameworkCore.Design NuGet package to your application. This is a breaking change required to support multiple database providers.

  3. Configure ignored tables via `TablesToIgnoreCommaDelimited`

    master

    The TablesToIgnoreCommaDelimited property in CompareEfSqlConfig controls which database tables or views are included in the comparison.

    • Default (null): Only checks tables/views that your EF Core entity classes are mapped to.
    • Empty string (""): Checks all tables and views in the database.
    • Comma-delimited list: Ignores specific tables/views. Use this if your database contains tables that EF Core does not access to prevent 'extra table' errors.

    Naming formats:

    • MyTable: Uses the database default schema.
    • dbo.MyTable: Specifies the dbo schema.

    Note: The comparison is case-insensitive.

    var config = new CompareEfSqlConfig
    {
        TablesToIgnoreCommaDelimited = "Orders,LineItem"
    };
    var comparer = new CompareEfSql(config);
  4. Force Stage 2 execution via `AlwaysRunStage2`

    master

    By default, CompareEfSql stops after Stage 1 if errors are found that have not been suppressed via IgnoreTheseErrors. If you need to see all errors (including those from Stage 2) even when Stage 1 errors exist, set the AlwaysRunStage2 boolean property to true in CompareEfSqlConfig.

    Note: This property requires version 8.2.0 or later.

    var config = new CompareEfSqlConfig
    {
        AlwaysRunStage2 = true
    };
  5. Suppress error categories via `AddIgnoreCompareLog`

    master

    For a more granular and programmatic approach, use AddIgnoreCompareLog on the CompareEfSqlConfig instance. This allows you to suppress entire categories of errors based on their CompareType, CompareState, and CompareAttributes. This is useful when you want to ignore all errors related to a specific property type, such as default value SQL settings, regardless of the specific entity or property name.

    var config = new CompareEfSqlConfig();
    // Suppresses any errors found on the default value set on a column
    config.AddIgnoreCompareLog(new CompareLog(CompareType.Property, CompareState.Different, null, CompareAttributes.DefaultValueSql));
    
    var comparer = new CompareEfSql(config);
    var hasErrors = comparer.CompareEfWithDb(context);
  6. Use CompareEfWithDb to compare EF Core with a database

    master

    The primary way to use the library is via the CompareEfWithDb method. You typically instantiate a comparer (e.g., CompareEfSql) and pass your DbContext to it. The method returns true if errors (mismatches or unused items) are found.

    [Fact]
    public void CompareViaContext()
    {
        // SETUP
        var options = //... with connection to database to check
        using (var context = new BookContext(options))
        {
            var comparer = new CompareEfSql();
    
            // ATTEMPT
            // This will compare EF Core model of the database with the database that the context's connection points to
            var hasErrors = comparer.CompareEfWithDb(context); 
    
            // VERIFY
            // The CompareEfWithDb method returns true if there were errors. 
            // The comparer.GetAllErrors property returns a string, with each error on a separate line
            hasErrors.ShouldBeFalse(comparer.GetAllErrors);
        }
    }
  7. Suppress error messages via `IgnoreTheseErrors`

    master

    If you encounter specific error messages that are incorrect or irrelevant (due to known limitations), you can suppress them by passing their exact error strings to the IgnoreTheseErrors method of CompareEfSqlConfig. Each error string should be separated by a newline ('\n') character. This is the easiest way to handle specific, known discrepancies.

    var config = new CompareEfSqlConfig();
    // Provide error strings separated by newlines
    config.IgnoreTheseErrors(@"DIFFERENT: Entity 'TptVer1', constraint name. Expected = PK_TptBases, found = PK_TptVer1
    DIFFERENT: TptVer1->Property 'Id', value generated. Expected = OnAdd, found = Never");
    
    var comparer = new CompareEfSql(config);
    var hasErrors = comparer.CompareEfWithDb(context);
  8. Compare multiple DbContexts against one database

    master

    The CompareEfWithDb method supports 'bounded contexts'. You can provide a connection string name (or a raw connection string) as the first parameter, followed by one or more DbContext instances. All provided contexts will be compared against the same database.

    [Fact]
    public void CompareBookThenOrderAgainstBookOrderDatabaseViaAppSettings()
    {
        // SETUP
        // ... I have left out how the options are created
        // This is the name of a connection string in the appsetting.json file in your test project
        const string connectionStringName = "BookOrderConnection";
        using (var context1 = new BookContext(options1))
        using (var context2 = new OrderContext(options2))
        {
            var comparer = new CompareEfSql();
    
            // ATTEMPT
            // It starts with the connection string/name and then can have as many contexts as you like
            var hasErrors = comparer.CompareEfWithDb(connectionStringName, context1, context2);
    
            // VERIFY
            hasErrors.ShouldBeFalse(comparer.GetAllErrors);
        }
    }
  9. Supported Database Providers

    master

    The supported database providers depend on the version of the library you are using:

    Version 8 and above

    Supports all EF Core database providers that can be scaffolded. Note: Some providers that do not follow the SQL Server style (e.g., CosmosDB) may report incorrect match errors.

    Versions below 8

    • SqlServer
    • Sqlite
    • Npgsql.EntityFrameworkCore.PostgreSQL
  10. Understand EfSchemaCompare error messages

    master

    Errors are retrieved via the comparer.GetAllErrors property as a single string with errors separated by Environment.NewLine.

    Error Prefixes

    • DIFFERENT: Indicates a mismatch between EF Core and the database (e.g., different data types).
    • NotInDatabase: Indicates something defined in EF Core is missing from the database.
    • ExtraInDatabase: Indicates something exists in the database (like an unused column or table) that is not in the EF Core model.

    Example Formats

    • Mismatch: "DIFFERENT: MyEntity->Property 'MyString'. Expected = varchar(max), found = nvarchar(max)"
    • Unused Item: "EXTRA IN DATABASE: Table 'HeadEntries', column name. Found = DifferentColumnName"

    Note: When an error mentions Table, it refers to either a SQL Table or a View.