Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ private static readonly MethodInfo KeyHasFillFactorMethodInfo
= typeof(SqlServerKeyBuilderExtensions).GetRuntimeMethod(
nameof(SqlServerKeyBuilderExtensions.HasFillFactor), [typeof(KeyBuilder), typeof(int)])!;

private static readonly MethodInfo KeyUseDataCompressionMethodInfo
= typeof(SqlServerKeyBuilderExtensions).GetRuntimeMethod(
nameof(SqlServerKeyBuilderExtensions.UseDataCompression), [typeof(KeyBuilder), typeof(DataCompressionType)])!;

private static readonly MethodInfo TableIsTemporalMethodInfo
= typeof(SqlServerTableBuilderExtensions).GetRuntimeMethod(
nameof(SqlServerTableBuilderExtensions.IsTemporal), [typeof(TableBuilder), typeof(bool)])!;
Expand Down Expand Up @@ -456,6 +460,8 @@ protected override bool IsHandledByConvention(IProperty property, IAnnotation an

SqlServerAnnotationNames.FillFactor => new MethodCallCodeFragment(KeyHasFillFactorMethodInfo, annotation.Value),

SqlServerAnnotationNames.DataCompression => new MethodCallCodeFragment(KeyUseDataCompressionMethodInfo, annotation.Value),

_ => null
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ public override void Generate(IKey key, CSharpRuntimeAnnotationCodeGeneratorPara
var annotations = parameters.Annotations;
annotations.Remove(SqlServerAnnotationNames.Clustered);
annotations.Remove(SqlServerAnnotationNames.FillFactor);
annotations.Remove(SqlServerAnnotationNames.DataCompression);
}

base.Generate(key, parameters);
Expand All @@ -156,6 +157,7 @@ public override void Generate(IUniqueConstraint uniqueConstraint, CSharpRuntimeA
var annotations = parameters.Annotations;
annotations.Remove(SqlServerAnnotationNames.Clustered);
annotations.Remove(SqlServerAnnotationNames.FillFactor);
annotations.Remove(SqlServerAnnotationNames.DataCompression);
}

base.Generate(uniqueConstraint, parameters);
Expand Down
27 changes: 27 additions & 0 deletions src/EFCore.SqlServer/EFCore.SqlServer.baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -1751,6 +1751,9 @@
{
"Type": "static class Microsoft.EntityFrameworkCore.SqlServerKeyBuilderExtensions",
"Methods": [
{
"Member": "static bool CanSetDataCompression(this Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionKeyBuilder keyBuilder, Microsoft.EntityFrameworkCore.DataCompressionType? dataCompressionType, bool fromDataAnnotation = false);"
},
{
"Member": "static bool CanSetFillFactor(this Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionKeyBuilder keyBuilder, int? fillFactor, bool fromDataAnnotation = false);"
},
Expand All @@ -1774,12 +1777,30 @@
},
{
"Member": "static Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionKeyBuilder? IsClustered(this Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionKeyBuilder keyBuilder, bool? clustered, bool fromDataAnnotation = false);"
},
{
"Member": "static Microsoft.EntityFrameworkCore.Metadata.Builders.KeyBuilder UseDataCompression(this Microsoft.EntityFrameworkCore.Metadata.Builders.KeyBuilder keyBuilder, Microsoft.EntityFrameworkCore.DataCompressionType dataCompressionType);"
},
{
"Member": "static Microsoft.EntityFrameworkCore.Metadata.Builders.KeyBuilder<TEntity> UseDataCompression<TEntity>(this Microsoft.EntityFrameworkCore.Metadata.Builders.KeyBuilder<TEntity> keyBuilder, Microsoft.EntityFrameworkCore.DataCompressionType dataCompressionType);"
},
{
"Member": "static Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionKeyBuilder? UseDataCompression(this Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionKeyBuilder keyBuilder, Microsoft.EntityFrameworkCore.DataCompressionType? dataCompressionType, bool fromDataAnnotation = false);"
}
]
},
{
"Type": "static class Microsoft.EntityFrameworkCore.SqlServerKeyExtensions",
"Methods": [
{
"Member": "static Microsoft.EntityFrameworkCore.DataCompressionType? GetDataCompression(this Microsoft.EntityFrameworkCore.Metadata.IReadOnlyKey key);"
},
{
"Member": "static Microsoft.EntityFrameworkCore.DataCompressionType? GetDataCompression(this Microsoft.EntityFrameworkCore.Metadata.IReadOnlyKey key, in Microsoft.EntityFrameworkCore.Metadata.StoreObjectIdentifier storeObject);"
},
{
"Member": "static Microsoft.EntityFrameworkCore.Metadata.ConfigurationSource? GetDataCompressionConfigurationSource(this Microsoft.EntityFrameworkCore.Metadata.IConventionKey key);"
},
{
"Member": "static int? GetFillFactor(this Microsoft.EntityFrameworkCore.Metadata.IReadOnlyKey key);"
},
Expand All @@ -1798,6 +1819,12 @@
{
"Member": "static bool? IsClustered(this Microsoft.EntityFrameworkCore.Metadata.IReadOnlyKey key, in Microsoft.EntityFrameworkCore.Metadata.StoreObjectIdentifier storeObject);"
},
{
"Member": "static void SetDataCompression(this Microsoft.EntityFrameworkCore.Metadata.IMutableKey key, Microsoft.EntityFrameworkCore.DataCompressionType? dataCompression);"
},
{
"Member": "static Microsoft.EntityFrameworkCore.DataCompressionType? SetDataCompression(this Microsoft.EntityFrameworkCore.Metadata.IConventionKey key, Microsoft.EntityFrameworkCore.DataCompressionType? dataCompression, bool fromDataAnnotation = false);"
},
{
"Member": "static void SetFillFactor(this Microsoft.EntityFrameworkCore.Metadata.IMutableKey key, int? fillFactor);"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,86 @@ public static bool CanSetFillFactor(
int? fillFactor,
bool fromDataAnnotation = false)
=> keyBuilder.CanSetAnnotation(SqlServerAnnotationNames.FillFactor, fillFactor, fromDataAnnotation);

/// <summary>
/// Configures whether the key is created with data compression option when targeting SQL Server.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
/// <see href="https://aka.ms/efcore-docs-sqlserver">Accessing SQL Server and Azure SQL databases with EF Core</see>
/// for more information and examples.
/// </remarks>
/// <param name="keyBuilder">The builder for the key being configured.</param>
/// <param name="dataCompressionType">A value indicating the data compression option to be used.</param>
/// <returns>A builder to further configure the key.</returns>
public static KeyBuilder UseDataCompression(this KeyBuilder keyBuilder, DataCompressionType dataCompressionType)
{
keyBuilder.Metadata.SetDataCompression(dataCompressionType);

return keyBuilder;
}

/// <summary>
/// Configures whether the key is created with data compression option when targeting SQL Server.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
/// <see href="https://aka.ms/efcore-docs-sqlserver">Accessing SQL Server and Azure SQL databases with EF Core</see>
/// for more information and examples.
/// </remarks>
/// <param name="keyBuilder">The builder for the key being configured.</param>
/// <param name="dataCompressionType">A value indicating the data compression option to be used.</param>
/// <returns>A builder to further configure the key.</returns>
public static KeyBuilder<TEntity> UseDataCompression<TEntity>(
this KeyBuilder<TEntity> keyBuilder,
DataCompressionType dataCompressionType)
=> (KeyBuilder<TEntity>)UseDataCompression((KeyBuilder)keyBuilder, dataCompressionType);

/// <summary>
/// Configures whether the key is created with data compression option when targeting SQL Server.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
/// <see href="https://aka.ms/efcore-docs-sqlserver">Accessing SQL Server and Azure SQL databases with EF Core</see>
/// for more information and examples.
/// </remarks>
/// <param name="keyBuilder">The builder for the key being configured.</param>
/// <param name="dataCompressionType">A value indicating the data compression option to be used.</param>
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
/// <returns>
/// The same builder instance if the configuration was applied,
/// <see langword="null" /> otherwise.
/// </returns>
public static IConventionKeyBuilder? UseDataCompression(
this IConventionKeyBuilder keyBuilder,
DataCompressionType? dataCompressionType,
bool fromDataAnnotation = false)
{
if (keyBuilder.CanSetDataCompression(dataCompressionType, fromDataAnnotation))
{
keyBuilder.Metadata.SetDataCompression(dataCompressionType, fromDataAnnotation);

return keyBuilder;
}

return null;
}

/// <summary>
/// Returns a value indicating whether the key can be configured with data compression option when targeting SQL Server.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
/// <see href="https://aka.ms/efcore-docs-sqlserver">Accessing SQL Server and Azure SQL databases with EF Core</see>
/// for more information and examples.
/// </remarks>
/// <param name="keyBuilder">The builder for the key being configured.</param>
/// <param name="dataCompressionType">A value indicating the data compression option to be used.</param>
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
/// <returns><see langword="true" /> if the key can be configured with data compression option when targeting SQL Server.</returns>
public static bool CanSetDataCompression(
this IConventionKeyBuilder keyBuilder,
DataCompressionType? dataCompressionType,
bool fromDataAnnotation = false)
=> keyBuilder.CanSetAnnotation(SqlServerAnnotationNames.DataCompression, dataCompressionType, fromDataAnnotation);
}
67 changes: 67 additions & 0 deletions src/EFCore.SqlServer/Extensions/SqlServerKeyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,71 @@ public static void SetFillFactor(this IMutableKey key, int? fillFactor)
/// <returns>The <see cref="ConfigurationSource" /> for whether the key uses the fill factor.</returns>
public static ConfigurationSource? GetFillFactorConfigurationSource(this IConventionKey key)
=> key.FindAnnotation(SqlServerAnnotationNames.FillFactor)?.GetConfigurationSource();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modify AreCompatibleForSqlServer to check that the configured values that end up on the same table key are not conflicting

/// <summary>
/// Returns the data compression that the key uses.
/// </summary>
/// <param name="key">The key.</param>
/// <returns>The data compression that the key uses.</returns>
public static DataCompressionType? GetDataCompression(this IReadOnlyKey key)
=> (key is RuntimeKey)
? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData)
: (DataCompressionType?)key[SqlServerAnnotationNames.DataCompression];

/// <summary>
/// Returns the data compression that the key uses.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="storeObject">The identifier of the store object.</param>
/// <returns>The data compression that the key uses.</returns>
public static DataCompressionType? GetDataCompression(this IReadOnlyKey key, in StoreObjectIdentifier storeObject)
{
if (key is RuntimeKey)
{
throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData);
}

var annotation = key.FindAnnotation(SqlServerAnnotationNames.DataCompression);
if (annotation != null)
{
return (DataCompressionType?)annotation.Value;
}

var sharedTableRootKey = key.FindSharedObjectRootKey(storeObject);
return sharedTableRootKey?.GetDataCompression(storeObject);
}

/// <summary>
/// Sets a value indicating the data compression the key uses.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="dataCompression">The value to set.</param>
public static void SetDataCompression(this IMutableKey key, DataCompressionType? dataCompression)
=> key.SetAnnotation(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use SetOrRemoveAnnotation. Also below.

SqlServerAnnotationNames.DataCompression,
dataCompression);

/// <summary>
/// Sets a value indicating the data compression the key uses.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="dataCompression">The value to set.</param>
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
/// <returns>The configured value.</returns>
public static DataCompressionType? SetDataCompression(
this IConventionKey key,
DataCompressionType? dataCompression,
bool fromDataAnnotation = false)
=> (DataCompressionType?)key.SetAnnotation(
SqlServerAnnotationNames.DataCompression,
dataCompression,
fromDataAnnotation)?.Value;

/// <summary>
/// Returns the <see cref="ConfigurationSource" /> for the data compression the key uses.
/// </summary>
/// <param name="key">The key.</param>
/// <returns>The <see cref="ConfigurationSource" /> for the data compression the key uses.</returns>
public static ConfigurationSource? GetDataCompressionConfigurationSource(this IConventionKey key)
=> key.FindAnnotation(SqlServerAnnotationNames.DataCompression)?.GetConfigurationSource();
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ protected override void ProcessKeyAnnotations(
{
annotations.Remove(SqlServerAnnotationNames.Clustered);
annotations.Remove(SqlServerAnnotationNames.FillFactor);
annotations.Remove(SqlServerAnnotationNames.DataCompression);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@ public override IEnumerable<IAnnotation> For(IUniqueConstraint constraint, bool
{
yield return new Annotation(SqlServerAnnotationNames.FillFactor, fillFactor);
}

if (key.GetDataCompression() is { } dataCompression)
{
yield return new Annotation(SqlServerAnnotationNames.DataCompression, dataCompression);
}
Comment on lines +179 to +182
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,44 @@ CONSTRAINT [AK_People_TheAlternateKey] UNIQUE ([TheAlternateKey]) WITH (FILLFACT
""");
}

[Fact]
public virtual async Task Create_table_with_data_compression()
{
await Test(
_ => { },
builder =>
{
builder.Entity("People").Property<int>("TheKey");
builder.Entity("People").Property<Guid>("TheAlternateKey");
builder.Entity("People").HasKey("TheKey").UseDataCompression(DataCompressionType.Page);
builder.Entity("People").HasAlternateKey("TheAlternateKey").UseDataCompression(DataCompressionType.Row);
},
model =>
{
var table = Assert.Single(model.Tables);

// DATA_COMPRESSION is not currently reverse-engineered, so it round-trips as null on the
// scaffolded model (same as the index case); the generated SQL below is the real assertion.
var primaryKey = table.PrimaryKey;
Assert.NotNull(primaryKey);
Assert.Null(primaryKey[SqlServerAnnotationNames.DataCompression]);

var uniqueConstraint = table.UniqueConstraints.FirstOrDefault();
Assert.NotNull(uniqueConstraint);
Assert.Null(uniqueConstraint[SqlServerAnnotationNames.DataCompression]);
});

AssertSql(
"""
CREATE TABLE [People] (
[TheKey] int NOT NULL IDENTITY,
[TheAlternateKey] uniqueidentifier NOT NULL,
CONSTRAINT [PK_People] PRIMARY KEY ([TheKey]) WITH (DATA_COMPRESSION = PAGE),
CONSTRAINT [AK_People_TheAlternateKey] UNIQUE ([TheAlternateKey]) WITH (DATA_COMPRESSION = ROW)
);
""");
}

public override async Task Drop_table()
{
await base.Drop_table();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,36 @@ public void Can_set_key_with_fillfactor_non_generic()
Assert.Equal(90, key.GetFillFactor());
}

[Fact]
public void Can_set_key_with_data_compression()
{
var modelBuilder = CreateConventionModelBuilder();

modelBuilder
.Entity<Customer>()
.HasKey(e => e.Id)
.UseDataCompression(DataCompressionType.Page);

var key = modelBuilder.Model.FindEntityType(typeof(Customer)).FindPrimaryKey();

Assert.Equal(DataCompressionType.Page, key.GetDataCompression());
}

[Fact]
public void Can_set_key_with_data_compression_non_generic()
{
var modelBuilder = CreateConventionModelBuilder();

modelBuilder
.Entity(typeof(Customer))
.HasKey("Id")
.UseDataCompression(DataCompressionType.Row);

var key = modelBuilder.Model.FindEntityType(typeof(Customer)).FindPrimaryKey();

Assert.Equal(DataCompressionType.Row, key.GetDataCompression());
}

[Fact]
public void Can_set_index_include()
{
Expand Down
Loading