Skip to content
105 changes: 104 additions & 1 deletion src/Libraries/Nop.Data/Extensions/FluentMigratorExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.Data;
using System.Linq.Expressions;
using System.Reflection;
using FluentMigrator.Builders;
using FluentMigrator.Builders.Alter;
using FluentMigrator.Builders.Alter.Table;
using FluentMigrator.Builders.Create;
using FluentMigrator.Builders.Create.Table;
using FluentMigrator.Builders.Delete;
using FluentMigrator.Builders.Delete.Column;
using FluentMigrator.Builders.Schema;
using FluentMigrator.Builders.Schema.Column;
using FluentMigrator.Builders.Schema.Table;
using FluentMigrator.Infrastructure.Extensions;
using FluentMigrator.Model;
using FluentMigrator.Runner;
Expand Down Expand Up @@ -63,7 +71,7 @@ private static void DefineByOwnType(string columnName, Type propType, CreateTabl
/// <param name="builder">The builder to add the database engine(s) to</param>
/// <returns>The migration runner builder</returns>
public static IMigrationRunnerBuilder AddNopDbEngines(this IMigrationRunnerBuilder builder)
{
{
if (!DataSettingsManager.IsDatabaseInstalled())
return builder.AddSqlServer().AddMySql5().AddPostgres92();

Expand Down Expand Up @@ -145,6 +153,101 @@ public static void TableFor<TEntity>(this ICreateExpressionRoot expressionRoot)
builder.RetrieveTableExpressions(type);
}

/// <summary>
/// Targets the entity's mapped table for a DELETE operation.
/// </summary>
/// <param name="expressionRoot">The root expression for a DELETE operation</param>
/// <typeparam name="TEntity">The entity type mapped to the database table</typeparam>
public static void TableFor<TEntity>(this IDeleteExpressionRoot expressionRoot) where TEntity : BaseEntity
{
var tableName = NameCompatibilityManager.GetTableName(typeof(TEntity));
expressionRoot.Table(tableName);
}

/// <summary>
/// Targets the entity's mapped table for an ALTER TABLE operation.
/// </summary>
/// <param name="expressionRoot">The root expression for an ALTER operation</param>
/// <typeparam name="TEntity">The entity type mapped to the database table</typeparam>
/// <returns>
/// A fluent syntax interface allowing further ALTER TABLE operations
/// such as adding or modifying columns.
/// </returns>
public static IAlterTableAddColumnOrAlterColumnOrSchemaOrDescriptionSyntax TableFor<TEntity>(this IAlterExpressionRoot expressionRoot) where TEntity : BaseEntity
{
var tableName = NameCompatibilityManager.GetTableName(typeof(TEntity));
return expressionRoot.Table(tableName);
}

/// <summary>
/// Targets the entity's mapped table for schema-related operations.
/// </summary>
/// <param name="expressionRoot">The root expression for schema inspection</param>
/// <typeparam name="TEntity">The entity type mapped to the database table</typeparam>
/// <returns>
/// A fluent syntax interface for performing schema operations
/// such as checking table or column existence.
/// </returns>
public static ISchemaTableSyntax TableFor<TEntity>(this ISchemaExpressionRoot expressionRoot) where TEntity : BaseEntity
{
var tableName = NameCompatibilityManager.GetTableName(typeof(TEntity));
return expressionRoot.Table(tableName);
}

/// <summary>
/// Targets a specific column of the entity's mapped table for schema inspection,
/// resolving the column name via <see cref="NameCompatibilityManager"/>.
/// </summary>
/// <typeparam name="TEntity">The entity type mapped to the database table</typeparam>
/// <param name="tableSchema">The schema table expression</param>
/// <param name="selector">An expression selecting the entity property</param>
/// <returns>
/// A fluent syntax interface for performing schema operations
/// such as checking column existence.
/// </returns>
public static ISchemaColumnSyntax ColumnFor<TEntity>(
this ISchemaTableSyntax tableSchema, Expression<Func<TEntity, object>> selector) where TEntity : BaseEntity
{
var property = ((MemberExpression)selector.Body)?.Member?.Name;
var columnName = NameCompatibilityManager.GetColumnName(typeof(TEntity), property!);
return tableSchema.Column(columnName);
}

/// <summary>
/// Adds a new column to the entity's mapped table for ALTER TABLE operations,
/// resolving the column name via <see cref="NameCompatibilityManager"/>.
/// </summary>
/// <typeparam name="TEntity">The entity type mapped to the database table</typeparam>
/// <param name="tableSchema">The alter table expression</param>
/// <param name="selector">An expression selecting the entity property</param>
/// <returns>
/// A fluent syntax interface allowing further ALTER TABLE operations
/// on the specified column.
/// </returns>
public static IAlterTableColumnAsTypeSyntax AddColumnFor<TEntity>(
this IAlterTableAddColumnOrAlterColumnSyntax tableSchema, Expression<Func<TEntity, object>> selector) where TEntity : BaseEntity
{
var property = ((MemberExpression)selector.Body)?.Member?.Name;
var columnName = NameCompatibilityManager.GetColumnName(typeof(TEntity), property!);
return tableSchema.AddColumn(columnName);
}

/// <summary>
/// Targets the mapped table of the specified entity for a DELETE COLUMN operation,
/// resolving the table name via <see cref="NameCompatibilityManager"/>.
/// </summary>
/// <typeparam name="TEntity">The entity type mapped to the database table</typeparam>
/// <param name="expressionRoot">The delete column expression from table syntax</param>
/// <returns>
/// A fluent syntax interface allowing the deletion of columns from the specified table.
/// </returns>
public static IInSchemaSyntax FromTable<TEntity>(
this IDeleteColumnFromTableSyntax expressionRoot) where TEntity : BaseEntity
{
var tableName = NameCompatibilityManager.GetTableName(typeof(TEntity));
return expressionRoot.FromTable(tableName);
}

/// <summary>
/// Retrieves expressions for building an entity table
/// </summary>
Expand Down
86 changes: 45 additions & 41 deletions src/Libraries/Nop.Data/Migrations/UpgradeTo490/SchemaMigration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,89 +15,93 @@ public class SchemaMigration : ForwardOnlyMigration
public override void Up()
{
//#7387
var productTableName = nameof(Product);

var ageVerificationColumnName = nameof(Product.AgeVerification);
if (!Schema.Table(productTableName).Column(ageVerificationColumnName).Exists())
if (!Schema.TableFor<Product>().ColumnFor<Product>(t => t.AgeVerification).Exists())
{
Alter.Table(productTableName)
.AddColumn(ageVerificationColumnName)
Alter.TableFor<Product>()
.AddColumnFor<Product>(t => t.AgeVerification)
.AsBoolean()
.NotNullable()
.WithDefaultValue(false);
}

var minimumAgeToPurchaseColumnName = nameof(Product.MinimumAgeToPurchase);
if (!Schema.Table(productTableName).Column(minimumAgeToPurchaseColumnName).Exists())
if (!Schema.TableFor<Product>().ColumnFor<Product>(t => t.MinimumAgeToPurchase).Exists())
{
Alter.Table(productTableName)
.AddColumn(minimumAgeToPurchaseColumnName)
Alter.TableFor<Product>()
.AddColumnFor<Product>(t => t.MinimumAgeToPurchase)
.AsInt32()
.NotNullable()
.WithDefaultValue(0);
}

//#7294
var topicTableName = nameof(Topic);
var topicAvailableEndDateColumnName = nameof(Topic.AvailableEndDateTimeUtc);
var topicAvailableStartDateColumnName = nameof(Topic.AvailableStartDateTimeUtc);

if (!Schema.Table(topicTableName).Column(topicAvailableEndDateColumnName).Exists())
if (!Schema.TableFor<Topic>().ColumnFor<Topic>(t => t.AvailableEndDateTimeUtc).Exists())
{
Alter.Table(topicTableName)
.AddColumn(topicAvailableEndDateColumnName)
Alter.TableFor<Topic>()
.AddColumnFor<Topic>(t => t.AvailableEndDateTimeUtc)
.AsDateTime()
.Nullable();
}

if (!Schema.Table(topicTableName).Column(topicAvailableStartDateColumnName).Exists())
if (!Schema.TableFor<Topic>().ColumnFor<Topic>(t => t.AvailableStartDateTimeUtc).Exists())
{
Alter.Table(topicTableName)
.AddColumn(topicAvailableStartDateColumnName)
Alter.TableFor<Topic>()
.AddColumnFor<Topic>(t => t.AvailableStartDateTimeUtc)
.AsDateTime()
.Nullable();
}

//#873
var productTagTableName = nameof(ProductTag);

if (!Schema.Table(productTagTableName).Column(nameof(ProductTag.MetaDescription)).Exists())
Alter.Table(productTagTableName).AddColumn(nameof(ProductTag.MetaDescription)).AsString().Nullable();
if (!Schema.TableFor<ProductTag>().ColumnFor<ProductTag>(t => t.MetaDescription).Exists())
{
Alter.TableFor<ProductTag>()
.AddColumnFor<ProductTag>(t => t.MetaDescription)
.AsString()
.Nullable();
}

if (!Schema.Table(productTagTableName).Column(nameof(ProductTag.MetaKeywords)).Exists())
Alter.Table(productTagTableName).AddColumn(nameof(ProductTag.MetaKeywords)).AsString(400).Nullable();
if (!Schema.TableFor<ProductTag>().ColumnFor<ProductTag>(t => t.MetaKeywords).Exists())
{
Alter.TableFor<ProductTag>()
.AddColumnFor<ProductTag>(t => t.MetaKeywords)
.AsString(400)
.Nullable();
}

if (!Schema.Table(productTagTableName).Column(nameof(ProductTag.MetaTitle)).Exists())
Alter.Table(productTagTableName).AddColumn(nameof(ProductTag.MetaTitle)).AsString(400).Nullable();
if (!Schema.TableFor<ProductTag>().ColumnFor<ProductTag>(t => t.MetaTitle).Exists())
{
Alter.TableFor<ProductTag>()
.AddColumnFor<ProductTag>(t => t.MetaTitle)
.AsString(400)
.Nullable();
}

//#7390
if (!Schema.Table(nameof(Menu)).Exists())
if (!Schema.TableFor<Menu>().Exists())
Create.TableFor<Menu>();

if (!Schema.Table(nameof(MenuItem)).Exists())
if (!Schema.TableFor<Menu>().Exists())
Create.TableFor<MenuItem>();

var footerColumn1ColumnName = "IncludeInFooterColumn1";
if (Schema.Table(topicTableName).Column(footerColumn1ColumnName).Exists())
Delete.Column(footerColumn1ColumnName).FromTable(topicTableName);
if (Schema.TableFor<Topic>().Column(footerColumn1ColumnName).Exists())
Delete.Column(footerColumn1ColumnName).FromTable<Topic>();

var footerColumn2ColumnName = "IncludeInFooterColumn2";
if (Schema.Table(topicTableName).Column(footerColumn2ColumnName).Exists())
Delete.Column(footerColumn2ColumnName).FromTable(topicTableName);
if (Schema.TableFor<Topic>().Column(footerColumn2ColumnName).Exists())
Delete.Column(footerColumn2ColumnName).FromTable<Topic>();

var footerColumn3ColumnName = "IncludeInFooterColumn3";
if (Schema.Table(topicTableName).Column(footerColumn3ColumnName).Exists())
Delete.Column(footerColumn3ColumnName).FromTable(topicTableName);
if (Schema.TableFor<Topic>().Column(footerColumn3ColumnName).Exists())
Delete.Column(footerColumn3ColumnName).FromTable<Topic>();

var includeTopicInTopMenuColumnName = "IncludeInTopMenu";
if (Schema.Table(topicTableName).Column(includeTopicInTopMenuColumnName).Exists())
Delete.Column(includeTopicInTopMenuColumnName).FromTable(topicTableName);
if (Schema.TableFor<Topic>().Column(includeTopicInTopMenuColumnName).Exists())
Delete.Column(includeTopicInTopMenuColumnName).FromTable<Topic>();

var categoryTableName = nameof(Category);
var includeCategoryInTopMenuColumnName = "IncludeInTopMenu";
if (Schema.Table(categoryTableName).Column(includeCategoryInTopMenuColumnName).Exists())
Delete.Column(includeCategoryInTopMenuColumnName).FromTable(categoryTableName);


if (Schema.TableFor<Category>().Column(includeCategoryInTopMenuColumnName).Exists())
Delete.Column(includeCategoryInTopMenuColumnName).FromTable<Category>();
}
}