Useful tool if you are changing the schema of your database's schema outside of EF Core' migrations, say by using SQL change scripts. See readme file on github.
$ dotnet add package EfCore.SchemaCompareIf you are changing the schema of your database's schema outside of EF Core' migrations, say by using SQL change scripts, then this library can quickly tell you if the a specific database schema and EF Core's Model of the database are in step. Versions below 8 this library
The first number in the version number of this library defines what version of NET framework it works for. e.g.
WARNING: Version 8 any above contains a BREAKING CHANGE. You need to add the Microsoft.EntityFrameworkCore.Design NuGet to your application when using this library. This is needed as the EfCore.SchemaCompare library now supports multiple database providers does - see What database providers does it support.
The EfCore.SchemaCompare library (shortened to EfSchemaCompare in the documentations) is available on NuGet as EfCore.SchemaCompare and is an open-source library under the MIT licence. See ReleaseNotes for details of changes and information on versions from EF Core 5 onwards.
TABLE OF CONTENT
CompareEfWithDb methodNOTE: I use the term entity class for classes mapped to the database by EF Core.
Thanks to GitHub @bgrauer-atacom and @lweberprb for suggesting that this library could support extra database providers. See the issue #26 to see the code that these two people provided to add this feature.
OnAdd for a int primary key which is provided by the database)The following are things I haven't bothered to check.
EfSchemaCompare uses two EF Core features to get EF Core's internal schema and the database's schema. They are
Model property in your application's DbContext. This provides the internal version of the database schema that EF Core builds from looking at the entity classes and any EF Core configuration attributes/methods.The fun part is comparing these two sources, especially with all the different types of configurations that EF Core can handle. The diagram shown below shows using EfSchemaCompare to check a test database that you updated with some SQL migration scripts against the current EF Core's Model.

The EfSchemaCompare uses two stages: Stage 1 checks your EF Core DbContext matches your database. Stage 2 checks your database for extra tables, columns, etc. that your EF Core DbContext doesn't use.
I usually run the EfSchemaCompare code in my unit tests, but that is up to you.
Here is a example of using the EfSchemaCompare feature
[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);
}
}
CompareEfWithDb methodCompareEfWithDb method can take multiple DbContexts, known as bounded contexts (see chapter 13, section 13.4.8 in my book Entity Framework Core in Action, second edition). You can add as many contexts and they are compared to one database.appsetting.json file.appsetting.json file, or there is no appsetting.json, then it assumes the string is a connection string.See below for an example of both of of these options:
[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
//Its starts with the connection string/name and then you can have as many contexts as you like
var hasErrors = comparer.CompareEfWithDb(connectionStringName, context1, context2);
//VERIFY
hasErrors.ShouldBeFalse(comparer.GetAllErrors);
}
}
The comparer.GetAllErrors property will return a string with each error separated by the Environment.NewLine string. Below is an example of an error
"DIFFERENT: MyEntity->Property 'MyString'. Expected = varchar(max), found = nvarchar(max)"
The error above says
DIFFERENT: There is a difference between EF Core and the database (other settings are NotInDatabase, ExtraInDatabase)MyEntity->Property 'MyString', column type gives a description of what was checkedExpected = varchar(max) says what EF Core thought it should befound = nvarchar(max) says what the database setting wasHere is another error coming from stage 2 where it checks the database side, i.e., Unused Tables, Columns and Indexes
EXTRA IN DATABASE: Table 'HeadEntries', column name. Found = DifferentColumnName
This says that there is a column called MyEntityId in the table MyEntites that hasn't got a property in the entity class mapped to the MyEntites table.
NOTE: When errors contain the word Table it can be a SQL Table or View.
In a few cases you will get errors that aren't correct (see limitations) or not relevant. In these cases you might want to suppress those errors. There are two way to do this, with the first being the easiest. Both use the CompareEfSqlConfig class.
IgnoreTheseErrorsIn this approach you capture the error strings you want to ignore and return them as a string, with each error separated by the newline, '\n', character. You feed the errors via the configuration's IgnoreTheseErrors method. See an example below
public void CompareTptContextSuppressViaIgnoreTheseErrors()
{
//SETUP
var options = this.CreateUniqueClassOptions<TptDbContext>();
using var context = new TptDbContext(options);
context.Database.EnsureClean();
var config = new CompareEfSqlConfig();
//This converts the error strings back CompareLog classes (see next example) which suppresses these errors
config.IgnoreTheseErrors(@"DIFFERENT: TptVer1->PrimaryKey 'PK_TptBases', constraint name. Expected = PK_TptBases, found = PK_TptVer1
DIFFERENT: TptVer1->Property 'Id', value generated. Expected = OnAdd, found = Never
DIFFERENT: TptVer1->Property 'MyVer1Int', nullability. Expected = NULL, found = NOT NULL
DIFFERENT: TptVer1->ForeignKey 'FK_TptVer1_TptBases_Id', delete behavior. Expected = ClientCascade, found = NoAction
DIFFERENT: Entity 'TptVer1', constraint name. Expected = PK_TptBases, found = PK_TptVer1
DIFFERENT: TptVer2->PrimaryKey 'PK_TptBases', constraint name. Expected = PK_TptBases, found = PK_TptVer2
DIFFERENT: TptVer2->Property 'Id', value generated. Expected = OnAdd, found = Never
DIFFERENT: TptVer2->Property 'MyVer2Int', nullability. Expected = NULL, found = NOT NULL
DIFFERENT: TptVer2->ForeignKey 'FK_TptVer2_TptBases_Id', delete behavior. Expected = ClientCascade, found = NoAction
DIFFERENT: Entity 'TptVer2', constraint name. Expected = PK_TptBases, found = PK_TptVer2");
var comparer = new CompareEfSql(config);
//ATTEMPT
var hasErrors = comparer.CompareEfWithDb(context);
//VERIFY
hasErrors.ShouldBeFalse(comparer.GetAllErrors);
}
}
AddIgnoreCompareLogThe other approach is useful when you want to suppress a general set of errors, but it is a bit complicated. Here is an example where it suppresses any errors found on the default value set on a column.
[Fact]
public void CompareSuppressViaViaAddIgnoreCompareLog()
{
//SETUP
var options = this.CreateUniqueClassOptions<BookContext>();
using var context = new BookContext(options);
context.Database.EnsureClean();
var config = new CompareEfSqlConfig
config.AddIgnoreCompareLog(new CompareLog(CompareType.Property, CompareState.Different, null, CompareAttributes.DefaultValueSql));
var comparer = new CompareEfSql(config);
//ATTEMPT
var hasErrors = comparer.CompareEfWithDb(context);
//VERIFY
hasErrors.ShouldBeFalse(comparer.GetAllErrors);
}
You have already seen the class called CompareEfSqlConfig for suppressing errors, but there are two other configrations.
TablesToIgnoreCommaDelimited string propertyYou have already seen the class called CompareEfSqlConfig for suppressing errors. There is one other configuration property called TablesToIgnoreCommaDelimited, which allows you to control what table/views in the database are considered.
By default (i.e. when TablesToIgnoreCommaDelimited is null) then CompareEfSql will only look at the tables/views in the database that your EF Core entity classes are mapped to. This provides an simple starting point. The other options are:
TablesToIgnoreCommaDelimited to "" (i.e. empty string)TablesToIgnoreCommaDelimited to a list of tables to ignore.CompareEfSql
about them, otherwise it will output a message saying there are extra tables you are not accessing from EF Core.
You do this by providing a comma delimited list of table names, with an optional schema name if needed.
Here are two examples of a table name
MyTable - this has no schema, so the default schema of the database will be useddbo.MyTable - this defines the schema to be dbo, - a full stop separates the schema name from the table name.NOTE: The comparison is case insensitive.
Here is an example of configuring the comparer to not look at the tables Orders and LineItem
var config = new CompareEfSqlConfig
{
TablesToIgnoreCommaDelimited = "Orders,LineItem"
};
var comparer = new CompareEfSql(config);
AlwaysRunStage2 boolean property (v8.2.0 or later)Getting all the errors in one go can be useful, for instance when you are creating a EF Core DbContext to match a given database. But by default, Stage 2 isn't run if Stage 1 found errors that haven't been register in config.IgnoreTheseErrors(... your error strings ...).
In version 8.2.0 a new boolean property called AlwaysRunStage2 in CompareEfSqlConfig and if you you set this to true then Stage 2 will always run, even if there are non-ignored errors. See issue #38 which made me add this new feature.