Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/github-actions-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
type: string
description: The version of the library
required: true
default: 2.1.0
default: 2.2.0
VersionSuffix:
type: string
description: The version suffix of the library (for example rc.1)
Expand Down
8 changes: 8 additions & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReleaseNotes>
2.2.0
- Add SqlServerDatabase.ClearDataAsync() method.
- Add SqlServer.CreateDatabase() method to create database from an Entity Framework DbContext.
- Add SqlServer.CreateEmptyDatabaseAsync() method.
- Add SqlServer.DeleteDatabaseAsync() method.
- Add SqlServerDatabase.ExecuteScriptAsync() method.
- Add SqlServerDatabase.InsertIntoAsync() method.

2.1.0
- PosInformatique.Testing.Databases.SqlServer target the .NET Standard 2.0 platform.
- PosInformatique.Testing.Databases.SqlServer.Dac target the .NET Core 6.0 and .NET Framework 4.6.2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,33 @@ namespace PosInformatique.Testing.Databases.SqlServer
public static class EntityFrameworkSqlServerExtensions
{
/// <summary>
/// Deploy a database using a DACPAC file.
/// Creates a database using the specified Entity Framework <paramref name="context"/>.
/// </summary>
/// <remarks>If a database already exists, it will be deleted.</remarks>
/// <remarks>If the database already exists, it will be deleted.</remarks>
/// <param name="server"><see cref="SqlServer"/> instance where the database will be created.</param>
/// <param name="name">Name of the database to create.</param>
/// <param name="context"><see cref="DbContext"/> which represents the database to create.</param>
/// <returns>An instance of the <see cref="SqlServerDatabase"/> which represents the deployed database.</returns>
public static SqlServerDatabase CreateDatabase(this SqlServer server, string name, DbContext context)
{
var database = server.GetDatabase(name);

context.Database.SetConnectionString(database.ConnectionString);

context.Database.EnsureDeleted();
context.Database.EnsureCreated();

return database;
}

/// <summary>
/// Creates a database using the specified Entity Framework <paramref name="context"/>.
/// </summary>
/// <remarks>If the database already exists, it will be deleted.</remarks>
/// <param name="server"><see cref="SqlServer"/> instance where the database will be created.</param>
/// <param name="name">Name of the database to create.</param>
/// <param name="context"><see cref="DbContext"/> which represents the database to create.</param>
/// <returns>A <see cref="Task"/> which represents the asynchronous operation and contains an instance of the <see cref="SqlServerDatabase"/> that represents the deployed database.</returns>
public static async Task<SqlServerDatabase> CreateDatabaseAsync(this SqlServer server, string name, DbContext context)
{
var database = server.GetDatabase(name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ internal interface ISqlObjectDifferencesVisitor
void Visit<TSqlObject>(SqlObjectDifferences<TSqlObject> differences)
where TSqlObject : SqlObject;

void Visit(SqlColumnDifferences differences);

void Visit(SqlForeignKeyDifferences differences);

void Visit(SqlIndexDifferences differences);
Expand Down
41 changes: 41 additions & 0 deletions src/Testing.Databases.SqlServer/Comparer/SqlColumnDifferences.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//-----------------------------------------------------------------------
// <copyright file="SqlColumnDifferences.cs" company="P.O.S Informatique">
// Copyright (c) P.O.S Informatique. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace PosInformatique.Testing.Databases
{
/// <summary>
/// Represents the differences of a <see cref="SqlColumn"/> between two databases.
/// </summary>
public class SqlColumnDifferences : SqlObjectDifferences<SqlColumn>
{
internal SqlColumnDifferences(
SqlColumn? source,
SqlColumn? target,
SqlObjectDifferenceType type,
IReadOnlyList<SqlObjectPropertyDifference>? properties,
SqlObjectDifferences<SqlDefaultConstraint>? defaultConstraint)
: base(source, target, type, properties)
{
this.DefaultConstraint = defaultConstraint;
}

internal SqlColumnDifferences(
SqlObjectDifferences<SqlColumn> differences)
: this(differences.Source, differences.Target, differences.Type, differences.Properties, null)
{
}

/// <summary>
/// Gets the difference of the columns in the foreign key compared.
/// </summary>
public SqlObjectDifferences<SqlDefaultConstraint>? DefaultConstraint { get; }

internal override void Accept(ISqlObjectDifferencesVisitor visitor)
{
visitor.Visit(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ public void Visit<TSqlObject>(SqlObjectDifferences<TSqlObject> differences)
}
}

public void Visit(SqlColumnDifferences differences)
{
this.Visit<SqlColumn>(differences);

this.Generate(differences.DefaultConstraint, "Default constraint");
}

public void Visit(SqlForeignKeyDifferences differences)
{
this.WriteProperties(differences.Properties);
Expand Down Expand Up @@ -98,13 +105,7 @@ public void Visit(SqlTableDifferences differences)

this.Generate(differences.Indexes, "Indexes");

if (differences.PrimaryKey is not null)
{
this.Indent();
this.WriteLine($"------ Primary key ------");
differences.PrimaryKey.Accept(this);
this.Unindent();
}
this.Generate(differences.PrimaryKey, "Primary key");

this.Generate(differences.Triggers, "Triggers");

Expand All @@ -121,6 +122,17 @@ public void Visit(SqlUniqueConstraintDifferences differences)
this.Generate(differences.Columns, "Columns");
}

private void Generate<TSqlObject>(SqlObjectDifferences<TSqlObject>? difference, string typeName)
where TSqlObject : SqlObject
{
if (difference is null)
{
return;
}

this.Generate([difference], typeName);
}

private void Generate<TSqlObject>(IEnumerable<SqlObjectDifferences<TSqlObject>> differences, string typeName)
where TSqlObject : SqlObject
{
Expand Down
77 changes: 47 additions & 30 deletions src/Testing.Databases.SqlServer/Comparer/SqlObjectComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,11 @@ public static IList<SqlObjectDifferences<TSqlObject>> Compare<TSqlObject>(IReadO
var keyValue = keySelector(targetObject);
var sourceObject = Find(source, keySelector, keyValue);

if (sourceObject is null)
{
// Missing in the source.
differences.Add(new SqlObjectDifferences<TSqlObject>(null, targetObject, SqlObjectDifferenceType.MissingInSource, null));
}
else
{
// Compare the object using visitor pattern.
var difference = Compare(sourceObject, targetObject);
var difference = Compare(sourceObject, targetObject);

if (difference is not null)
{
differences.Add(difference);
}
if (difference is not null)
{
differences.Add(difference);
}
}

Expand All @@ -61,7 +52,7 @@ public static IList<SqlObjectDifferences<TSqlObject>> Compare<TSqlObject>(IReadO

public static IList<SqlTableDifferences> Compare(IReadOnlyList<SqlTable> source, IReadOnlyList<SqlTable> target)
{
return Compare(source, target, t => t.Name, diff => new SqlTableDifferences(diff) { PrimaryKey = null });
return Compare(source, target, t => t.Name, diff => new SqlTableDifferences(diff));
}

public SqlObjectDifferences? Visit(SqlCheckConstraint checkConstraint)
Expand All @@ -73,8 +64,10 @@ public static IList<SqlTableDifferences> Compare(IReadOnlyList<SqlTable> source,

public SqlObjectDifferences? Visit(SqlColumn column)
{
return this.CreateDifferences(
column,
var sourceColumn = (SqlColumn)this.source;

// Compare the properties
var differenceProperties = GetPropertyDifferences(
this.CompareProperty(column, t => t.Position, nameof(column.Position)),
this.CompareProperty(column, t => t.MaxLength, nameof(column.MaxLength)),
this.CompareProperty(column, t => t.Precision, nameof(column.Precision)),
Expand All @@ -84,6 +77,24 @@ public static IList<SqlTableDifferences> Compare(IReadOnlyList<SqlTable> source,
this.CompareProperty(column, t => t.CollationName, nameof(column.CollationName)),
this.CompareProperty(column, t => t.IsComputed, nameof(column.IsComputed)),
this.CompareProperty(column, t => TsqlCodeHelper.RemoveNotUsefulCharacters(t.ComputedExpression), nameof(column.ComputedExpression), t => t.ComputedExpression));

// Compare the default constraint
var defaultConstraintDifference = Compare(sourceColumn.DefaultConstraint, column.DefaultConstraint);

if (differenceProperties.Count > 0 || defaultConstraintDifference != null)
{
return new SqlColumnDifferences((SqlColumn)this.source, column, SqlObjectDifferenceType.Different, differenceProperties, defaultConstraintDifference);
}

return null;
}

public SqlObjectDifferences? Visit(SqlDefaultConstraint defaultConstraint)
{
return this.CreateDifferences(
defaultConstraint,
this.CompareProperty(defaultConstraint, df => df.Name, nameof(defaultConstraint.Name)),
this.CompareProperty(defaultConstraint, df => TsqlCodeHelper.RemoveNotUsefulCharacters(df.Expression), nameof(defaultConstraint.Expression), df => df.Expression));
}

public SqlObjectDifferences? Visit(SqlForeignKey foreignKey)
Expand Down Expand Up @@ -191,7 +202,7 @@ public static IList<SqlTableDifferences> Compare(IReadOnlyList<SqlTable> source,
var checkConstraintDifferences = Compare(sourceTable.CheckConstraints, table.CheckConstraints, tr => tr.Name);

// Compare the columns
var columnsDifferences = Compare(sourceTable.Columns, table.Columns, c => c.Name);
var columnsDifferences = Compare(sourceTable.Columns, table.Columns, c => c.Name, diff => new SqlColumnDifferences(diff));

// Compare the foreign keys
var foreignKeysDifferences = Compare(sourceTable.ForeignKeys, table.ForeignKeys, fk => fk.Name, diff => new SqlForeignKeyDifferences(diff));
Expand All @@ -200,7 +211,7 @@ public static IList<SqlTableDifferences> Compare(IReadOnlyList<SqlTable> source,
var indexesDifferences = Compare(sourceTable.Indexes, table.Indexes, i => i.Name, diff => new SqlIndexDifferences(diff));

// Compare the primary key
var primaryKeyDifferences = (SqlPrimaryKeyDifferences?)Compare(CreateArray(sourceTable.PrimaryKey), CreateArray(table.PrimaryKey), pk => pk.Name).SingleOrDefault();
var primaryKeyDifferences = (SqlPrimaryKeyDifferences?)Compare(sourceTable.PrimaryKey, table.PrimaryKey);

// Compare the triggers
var triggersDifferences = Compare(sourceTable.Triggers, table.Triggers, tr => tr.Name);
Expand Down Expand Up @@ -262,9 +273,26 @@ public static IList<SqlTableDifferences> Compare(IReadOnlyList<SqlTable> source,
this.CompareProperty(view, v => TsqlCodeHelper.RemoveNotUsefulCharacters(v.Code), nameof(view.Code), v => v.Code));
}

private static SqlObjectDifferences<TSqlObject>? Compare<TSqlObject>(TSqlObject source, TSqlObject target)
private static SqlObjectDifferences<TSqlObject>? Compare<TSqlObject>(TSqlObject? source, TSqlObject? target)
where TSqlObject : SqlObject
{
if (source is null)
{
if (target is null)
{
return null;
}

return new SqlObjectDifferences<TSqlObject>(null, target, SqlObjectDifferenceType.MissingInSource, null);
}
else
{
if (target is null)
{
return new SqlObjectDifferences<TSqlObject>(source, null, SqlObjectDifferenceType.MissingInTarget, null);
}
}

var visitor = new SqlObjectComparer(source);

return (SqlObjectDifferences<TSqlObject>?)target.Accept(visitor);
Expand Down Expand Up @@ -303,17 +331,6 @@ private static IReadOnlyList<SqlObjectPropertyDifference> GetPropertyDifferences
return objects.SingleOrDefault(o => Equals(keySelector(o), value));
}

private static T[] CreateArray<T>(T? value)
where T : class
{
if (value is null)
{
return [];
}

return [value];
}

private SqlObjectPropertyDifference? CompareProperty<TSqlObject>(TSqlObject target, Func<TSqlObject, object?> propertyValueForComparison, string name, Func<TSqlObject, object?>? propertyValueToDisplay = null)
where TSqlObject : SqlObject
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ internal SqlPrimaryKeyDifferences(
this.Columns = new ReadOnlyCollection<SqlObjectDifferences<SqlPrimaryKeyColumn>>(columns);
}

internal SqlPrimaryKeyDifferences(
SqlObjectDifferences<SqlPrimaryKey> differences)
: this(differences.Source, differences.Target, differences.Type, differences.Properties, [])
{
}

/// <summary>
/// Gets the difference of the columns in the primary key compared.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ internal SqlTableDifferences(
SqlTable? target,
SqlObjectDifferenceType type,
IReadOnlyList<SqlObjectPropertyDifference>? properties,
IList<SqlObjectDifferences<SqlColumn>> columns,
IList<SqlColumnDifferences> columns,
IList<SqlObjectDifferences<SqlTrigger>> triggers,
IList<SqlObjectDifferences<SqlCheckConstraint>> checkConstraints,
IList<SqlIndexDifferences> indexes,
IList<SqlForeignKeyDifferences> foreignKeys,
IList<SqlUniqueConstraintDifferences> uniqueConstraints)
: base(source, target, type, properties)
{
this.Columns = new ReadOnlyCollection<SqlObjectDifferences<SqlColumn>>(columns);
this.Columns = new ReadOnlyCollection<SqlColumnDifferences>(columns);
this.Triggers = new ReadOnlyCollection<SqlObjectDifferences<SqlTrigger>>(triggers);
this.CheckConstraints = new ReadOnlyCollection<SqlObjectDifferences<SqlCheckConstraint>>(checkConstraints);
this.Indexes = new ReadOnlyCollection<SqlIndexDifferences>(indexes);
Expand All @@ -48,7 +48,7 @@ internal SqlTableDifferences(
/// <summary>
/// Gets the columns differences between the two SQL tables.
/// </summary>
public ReadOnlyCollection<SqlObjectDifferences<SqlColumn>> Columns { get; }
public ReadOnlyCollection<SqlColumnDifferences> Columns { get; }

/// <summary>
/// Gets the indexes differences between the two SQL tables.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ public interface ISqlObjectVisitor<TResult>
/// <returns>The result of the visit.</returns>
TResult Visit(SqlColumn column);

/// <summary>
/// Visits the specified <paramref name="defaultConstraint"/>.
/// </summary>
/// <param name="defaultConstraint"><see cref="SqlDefaultConstraint"/> to visit.</param>
/// <returns>The result of the visit.</returns>
TResult Visit(SqlDefaultConstraint defaultConstraint);

/// <summary>
/// Visits the specified <paramref name="foreignKey"/>.
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions src/Testing.Databases.SqlServer/ObjectModel/SqlColumn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ internal SqlColumn(
/// </summary>
public string? ComputedExpression { get; internal set; }

/// <summary>
/// Gets the default constraint of the column.
/// </summary>
public SqlDefaultConstraint? DefaultConstraint { get; internal set; }

/// <inheritdoc />
public override TResult Accept<TResult>(ISqlObjectVisitor<TResult> visitor) => visitor.Visit(this);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//-----------------------------------------------------------------------
// <copyright file="SqlDefaultConstraint.cs" company="P.O.S Informatique">
// Copyright (c) P.O.S Informatique. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace PosInformatique.Testing.Databases
{
/// <summary>
/// Represents a default constraint of a <see cref="SqlColumn" />.
/// </summary>
public class SqlDefaultConstraint : SqlObject
{
internal SqlDefaultConstraint(string name, string expression)
{
this.Name = name;
this.Expression = expression;
}

/// <summary>
/// Gets the name of the default constraint.
/// </summary>
public string Name { get; }

/// <summary>
/// Gets the expression of the default constraint.
/// </summary>
public string Expression { get; }

/// <inheritdoc />
public override TResult Accept<TResult>(ISqlObjectVisitor<TResult> visitor) => visitor.Visit(this);

/// <inheritdoc cref="Name"/>
public override string ToString()
{
return this.Name;
}
}
}
Loading
Loading