Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(efcore3): add support for efcore v3
- Loading branch information
Showing
5 changed files
with
354 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
26 changes: 26 additions & 0 deletions
26
...c/entityframeworkcore3.validation/Aranasoft.Cobweb.EntityFrameworkCore3.Validation.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<Import Project="..\..\..\..\netfx.props" /> | ||
|
||
<PropertyGroup> | ||
<TargetFrameworks>netstandard2.1</TargetFrameworks> | ||
<AssemblyName>Aranasoft.Cobweb.EntityFrameworkCore.Validation</AssemblyName> | ||
<RootNamespace>Aranasoft.Cobweb.EntityFrameworkCore.Validation</RootNamespace> | ||
<VersionPrefix>1.30.0</VersionPrefix> | ||
</PropertyGroup> | ||
|
||
<PropertyGroup> | ||
<Description>Schema validation and testing components for Entity Framework Core.</Description> | ||
<PackageTags>entityframeworkcore;efcore;validation;$(PackageTags)</PackageTags> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<Compile Include="..\entityframeworkcore2.validation\SchemaValidationException.cs" Link="SchemaValidationException.cs" /> | ||
<Compile Include="..\entityframeworkcore2.validation\SchemaValidationOptions.cs" Link="SchemaValidationOptions.cs" /> | ||
<Compile Include="..\entityframeworkcore2.validation\SchemaValidatorExtensions.cs" Link="SchemaValidatorExtensions.cs" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="[3,4)" /> | ||
</ItemGroup> | ||
|
||
</Project> |
107 changes: 107 additions & 0 deletions
107
src/entityframeworkcore/src/entityframeworkcore3.validation/SchemaComparison.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
using System.Linq; | ||
using Microsoft.EntityFrameworkCore; | ||
using Microsoft.EntityFrameworkCore.Metadata; | ||
using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; | ||
|
||
namespace Aranasoft.Cobweb.EntityFrameworkCore.Validation { | ||
internal static class SchemaComparison { | ||
public static bool TableExists(this DatabaseModel model, IEntityType type) { | ||
return TableExists(model, type.GetSchema() ?? model.DefaultSchema, type.GetTableName()); | ||
} | ||
|
||
public static bool TableExists(this DatabaseModel model, string schema, string tableName) { | ||
var checkedSchema = schema ?? model.DefaultSchema; | ||
return model.Tables.Any(table => table.Schema == checkedSchema && table.Name == tableName); | ||
} | ||
|
||
public static DatabaseTable GetTable(this DatabaseModel model, IEntityType type) { | ||
return GetTable(model, type.GetSchema() ?? model.DefaultSchema, type.GetTableName()); | ||
} | ||
|
||
public static DatabaseTable GetTable(DatabaseModel model, string schema, string tableName) { | ||
var checkedSchema = schema ?? model.DefaultSchema; | ||
return model.Tables.FirstOrDefault(table => table.Schema == checkedSchema && table.Name == tableName); | ||
} | ||
|
||
public static bool ColumnExists(this DatabaseModel model, IProperty property) { | ||
return ColumnExists(model, property.DeclaringEntityType, property); | ||
} | ||
|
||
public static bool ColumnExists(this DatabaseModel model, | ||
IEntityType type, | ||
IProperty property) { | ||
return ColumnExists(model, type.GetSchema(), type.GetTableName(), property.GetColumnName()); | ||
} | ||
|
||
public static bool ColumnExists(this DatabaseModel model, string schema, string tableName, string columnName) { | ||
var tableModel = GetTable(model, schema, tableName); | ||
return tableModel != null && tableModel.Columns.Any(column => column.Name == columnName); | ||
} | ||
|
||
public static DatabaseColumn GetColumn(this DatabaseModel model, IProperty property) { | ||
return GetColumn(model, property.DeclaringEntityType, property); | ||
} | ||
|
||
public static DatabaseColumn GetColumn(this DatabaseModel model, IEntityType type, IProperty property) { | ||
return GetColumn(model, type.GetSchema(), type.GetTableName(), property.GetColumnName()); | ||
} | ||
|
||
public static DatabaseColumn GetColumn(this DatabaseModel model, | ||
string schema, | ||
string tableName, | ||
string columnName) { | ||
var tableModel = GetTable(model, schema, tableName); | ||
return tableModel?.Columns.FirstOrDefault(column => column.Name == columnName); | ||
} | ||
|
||
public static bool IndexExists(this DatabaseModel model, IIndex index) { | ||
var entityType = index.DeclaringEntityType; | ||
return IndexExists(model, entityType.GetSchema(), entityType.GetTableName(), index.GetName()); | ||
} | ||
|
||
public static bool IndexExists(this DatabaseModel model, string schema, string tableName, string indexName) { | ||
var tableModel = GetTable(model, schema, tableName); | ||
return tableModel != null && tableModel.Indexes.Any(column => column.Name == indexName); | ||
} | ||
|
||
public static DatabaseIndex GetIndex(this DatabaseModel model, IIndex index) { | ||
var entityType = index.DeclaringEntityType; | ||
return GetIndex(model, entityType.GetSchema(), entityType.GetTableName(), index.GetName()); | ||
} | ||
|
||
public static DatabaseIndex GetIndex(this DatabaseModel model, | ||
string schema, | ||
string tableName, | ||
string indexName) { | ||
var tableModel = GetTable(model, schema, tableName); | ||
return tableModel?.Indexes.FirstOrDefault(index => index.Name == indexName); | ||
} | ||
|
||
public static bool ForeignKeyExists(this DatabaseModel model, | ||
IForeignKey foreignKey) { | ||
var entityType = foreignKey.DeclaringEntityType; | ||
return ForeignKeyExists(model, entityType.GetSchema(), entityType.GetTableName(), foreignKey.GetConstraintName()); | ||
} | ||
|
||
public static bool ForeignKeyExists(this DatabaseModel model, | ||
string schema, | ||
string tableName, | ||
string foreignKeyName) { | ||
var tableModel = GetTable(model, schema, tableName); | ||
return tableModel != null && tableModel.ForeignKeys.Any(column => column.Name == foreignKeyName); | ||
} | ||
|
||
public static DatabaseForeignKey GetForeignKey(this DatabaseModel model, IForeignKey foreignKey) { | ||
var entityType = foreignKey.DeclaringEntityType; | ||
return GetForeignKey(model, entityType.GetSchema(), entityType.GetTableName(), foreignKey.GetConstraintName()); | ||
} | ||
|
||
public static DatabaseForeignKey GetForeignKey(this DatabaseModel model, | ||
string schema, | ||
string tableName, | ||
string foreignKeyName) { | ||
var tableModel = GetTable(model, schema, tableName); | ||
return tableModel?.ForeignKeys.FirstOrDefault(index => index.Name == foreignKeyName); | ||
} | ||
} | ||
} |
112 changes: 112 additions & 0 deletions
112
src/entityframeworkcore/src/entityframeworkcore3.validation/SchemaValidator.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using Microsoft.EntityFrameworkCore; | ||
using Microsoft.EntityFrameworkCore.Infrastructure; | ||
using Microsoft.EntityFrameworkCore.Metadata; | ||
using Microsoft.EntityFrameworkCore.Scaffolding; | ||
using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; | ||
|
||
namespace Aranasoft.Cobweb.EntityFrameworkCore.Validation { | ||
public class SchemaValidator { | ||
protected DbContext Context { get; } | ||
|
||
public SchemaValidator(DbContext context) { | ||
Context = context; | ||
} | ||
|
||
public void ValidateSchema(SchemaValidationOptions validationOptions = null) { | ||
if (validationOptions == null) { | ||
validationOptions = new SchemaValidationOptions(); | ||
} | ||
|
||
var databaseModel = GetDatabaseModel(); | ||
|
||
var entityModel = Context.Model; | ||
var validationErrors = new List<string>(); | ||
var persistedTypes = entityModel.GetEntityTypes().Where(entityType => entityType.FindPrimaryKey() != null); | ||
|
||
foreach (var persistedType in persistedTypes) { | ||
var dbTable = databaseModel.GetTable(persistedType); | ||
|
||
if (dbTable == null) { | ||
validationErrors.Add($"Missing table: {persistedType.GetTableName()}"); | ||
continue; | ||
} | ||
|
||
validationErrors.AddRange(ValidateColumns(databaseModel, persistedType)); | ||
|
||
if (validationOptions.ValidateIndexes) { | ||
validationErrors.AddRange(ValidateIndexes(databaseModel, persistedType)); | ||
} | ||
|
||
if (validationOptions.ValidateForeignKeys) { | ||
validationErrors.AddRange(ValidateForeignKeys(databaseModel, persistedType)); | ||
} | ||
} | ||
|
||
if (validationErrors.Count > 0) { | ||
throw new SchemaValidationException("Schema validation failed", validationErrors); | ||
} | ||
} | ||
|
||
private List<string> ValidateColumns(DatabaseModel databaseModel, IEntityType persistedType) { | ||
var valErrors = new List<string>(); | ||
foreach (var persistedColumn in persistedType.GetProperties()) { | ||
var dbColumn = databaseModel.GetColumn(persistedColumn); | ||
if (dbColumn == null) { | ||
valErrors.Add($"Missing column: {persistedColumn.GetColumnName()} in {persistedType.GetTableName()}"); | ||
continue; | ||
} | ||
|
||
var columnTypesMatch = | ||
dbColumn.StoreType.Equals(persistedColumn.GetColumnType(), StringComparison.OrdinalIgnoreCase); | ||
if (!columnTypesMatch) { | ||
valErrors.Add( | ||
$"Column type mismatch in {persistedType.GetTableName()} for column {persistedColumn.GetColumnName()}. Found: {dbColumn.StoreType.ToLowerInvariant()}, Expected {persistedColumn.GetColumnType().ToLowerInvariant()}"); | ||
} | ||
|
||
if (persistedColumn.IsNullable != dbColumn.IsNullable) { | ||
valErrors.Add( | ||
$"Column nullability mismatch in {persistedType.GetTableName()} for column {persistedColumn.GetColumnName()}. Found: {(dbColumn.IsNullable ? "Nullable" : "NotNullable")}, Expected {(persistedColumn.IsNullable ? "Nullable" : "NotNullable")}"); | ||
} | ||
} | ||
|
||
return valErrors; | ||
} | ||
|
||
private IEnumerable<string> ValidateIndexes(DatabaseModel databaseModel, IEntityType persistedType) { | ||
var validationErrors = new List<string>(); | ||
|
||
foreach (var index in persistedType.GetIndexes()) { | ||
var dbIndex = databaseModel.GetIndex(index); | ||
if (dbIndex == null) { | ||
validationErrors.Add( | ||
$"Missing index: {index.GetName()} on {persistedType.GetTableName()}"); | ||
} | ||
} | ||
|
||
return validationErrors; | ||
} | ||
|
||
private IEnumerable<string> ValidateForeignKeys(DatabaseModel databaseModel, IEntityType persistedType) { | ||
var validationErrors = new List<string>(); | ||
|
||
foreach (var foreignKey in persistedType.GetForeignKeys()) { | ||
var databaseForeignKey = databaseModel.GetForeignKey(foreignKey); | ||
if (databaseForeignKey == null) { | ||
validationErrors.Add( | ||
$"Missing Foreign Key: {foreignKey.GetConstraintName()} on {persistedType.GetTableName()}"); | ||
} | ||
} | ||
|
||
return validationErrors; | ||
} | ||
|
||
private DatabaseModel GetDatabaseModel() { | ||
var factory = Context.GetService<IDatabaseModelFactory>(); | ||
var databaseModel = factory.Create(Context.Database.GetDbConnection(), new DatabaseModelFactoryOptions()); | ||
return databaseModel; | ||
} | ||
} | ||
} |
Oops, something went wrong.