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
54 changes: 50 additions & 4 deletions src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ public RelationalTypeMappingInfo(
int? scale)
{
// Note: Empty string is allowed for store type name because SQLite
_coreTypeMappingInfo = new TypeMappingInfo(null, null, false, unicode, size, null, precision, scale);
_coreTypeMappingInfo = new TypeMappingInfo(null, null, false, unicode, size, null, precision, scale, false);
StoreTypeName = storeTypeName;
StoreTypeNameBase = storeTypeNameBase;
IsFixedLength = null;
Expand Down Expand Up @@ -225,6 +225,42 @@ public RelationalTypeMappingInfo(
/// <param name="precision">Specifies a precision for the mapping, or <see langword="null" /> for default.</param>
/// <param name="scale">Specifies a scale for the mapping, or <see langword="null" /> for default.</param>
/// <param name="dbType">The suggested <see cref="DbType" />, or <see langword="null" /> for default.</param>
[Obsolete("Use overload that takes 'key' parameter.")]
public RelationalTypeMappingInfo(
Type? type,
RelationalTypeMapping? elementTypeMapping,
string? storeTypeName,
string? storeTypeNameBase,
bool keyOrIndex,
bool? unicode,
int? size,
bool? rowVersion,
bool? fixedLength,
int? precision,
int? scale,
DbType? dbType)
: this(
type, elementTypeMapping, storeTypeName, storeTypeNameBase, keyOrIndex, unicode, size, rowVersion, fixedLength,
precision, scale, dbType, keyOrIndex)
{
}

/// <summary>
/// Creates a new instance of <see cref="TypeMappingInfo" />.
/// </summary>
/// <param name="type">The CLR type in the model for which mapping is needed.</param>
/// <param name="elementTypeMapping">The type mapping for elements, if known.</param>
/// <param name="storeTypeName">The database type name.</param>
/// <param name="storeTypeNameBase">The provider-specific relational type name, with any facets removed.</param>
/// <param name="keyOrIndex">If <see langword="true" />, then a special mapping for a key or index may be returned.</param>
/// <param name="unicode">Specifies Unicode or ANSI mapping, or <see langword="null" /> for default.</param>
/// <param name="size">Specifies a size for the mapping, or <see langword="null" /> for default.</param>
/// <param name="rowVersion">Specifies a row-version, or <see langword="null" /> for default.</param>
/// <param name="fixedLength">Specifies a fixed length mapping, or <see langword="null" /> for default.</param>
/// <param name="precision">Specifies a precision for the mapping, or <see langword="null" /> for default.</param>
/// <param name="scale">Specifies a scale for the mapping, or <see langword="null" /> for default.</param>
/// <param name="dbType">The suggested <see cref="DbType" />, or <see langword="null" /> for default.</param>
/// <param name="key">If <see langword="true" />, then a special mapping for a key may be returned.</param>
public RelationalTypeMappingInfo(
Type? type = null,
RelationalTypeMapping? elementTypeMapping = null,
Expand All @@ -237,9 +273,10 @@ public RelationalTypeMappingInfo(
bool? fixedLength = null,
int? precision = null,
int? scale = null,
DbType? dbType = null)
DbType? dbType = null,
bool key = false)
{
_coreTypeMappingInfo = new TypeMappingInfo(type, elementTypeMapping, keyOrIndex, unicode, size, rowVersion, precision, scale);
_coreTypeMappingInfo = new TypeMappingInfo(type, elementTypeMapping, keyOrIndex, unicode, size, rowVersion, precision, scale, key);

IsFixedLength = fixedLength;
StoreTypeName = storeTypeName;
Expand Down Expand Up @@ -301,7 +338,16 @@ public int? Scale
public DbType? DbType { get; init; }

/// <summary>
/// Indicates whether or not the mapping is part of a key or index.
/// Indicates whether or not the mapping is part of a key or foreign key.
/// </summary>
public bool IsKey
{
get => _coreTypeMappingInfo.IsKey;
init => _coreTypeMappingInfo = _coreTypeMappingInfo with { IsKey = value };
}

/// <summary>
/// Indicates whether or not the mapping is part of a key, foreign key, or index.
/// </summary>
public bool IsKeyOrIndex
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;
/// </summary>
public class SqlServerTypeMappingSource : RelationalTypeMappingSource
{
private static readonly bool UseOldBehavior32898 =
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32898", out var enabled32898) && enabled32898;

private static readonly SqlServerFloatTypeMapping RealAlias
= new("placeholder", storeTypePostfix: StoreTypePostfix.None);

Expand Down Expand Up @@ -338,7 +341,7 @@ public SqlServerTypeMappingSource(
size: size,
fixedLength: isFixedLength,
storeTypePostfix: storeTypeName == null ? StoreTypePostfix.Size : StoreTypePostfix.None,
useKeyComparison: mappingInfo.IsKeyOrIndex);
useKeyComparison: UseOldBehavior32898 ? mappingInfo.IsKeyOrIndex : mappingInfo.IsKey);
}

if (clrType == typeof(byte[]))
Expand Down
42 changes: 38 additions & 4 deletions src/EFCore/Storage/TypeMappingInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,8 @@ public TypeMappingInfo(
var property = principals[0];

ElementTypeMapping = property.GetElementType()?.FindTypeMapping();
IsKeyOrIndex = property.IsKey() || property.IsForeignKey() || property.IsIndex();
IsKey = property.IsKey() || property.IsForeignKey();
IsKeyOrIndex = IsKey || property.IsIndex();
Size = fallbackSize ?? mappingHints?.Size;
IsUnicode = fallbackUnicode ?? mappingHints?.IsUnicode;
IsRowVersion = property is { IsConcurrencyToken: true, ValueGenerated: ValueGenerated.OnAddOrUpdate };
Expand Down Expand Up @@ -227,6 +228,32 @@ public TypeMappingInfo(
/// <param name="rowVersion">Specifies a row-version, or <see langword="null" /> for default.</param>
/// <param name="precision">Specifies a precision for the mapping, or <see langword="null" /> for default.</param>
/// <param name="scale">Specifies a scale for the mapping, or <see langword="null" /> for default.</param>
[Obsolete("Use overload that takes 'key' parameter.")]
public TypeMappingInfo(
Type? type,
CoreTypeMapping? elementTypeMapping,
bool keyOrIndex,
bool? unicode,
int? size,
bool? rowVersion,
int? precision,
int? scale)
: this(type, elementTypeMapping, keyOrIndex, unicode, size, rowVersion, precision, scale, false)
{
}

/// <summary>
/// Creates a new instance of <see cref="TypeMappingInfo" />.
/// </summary>
/// <param name="type">The CLR type in the model for which mapping is needed.</param>
/// <param name="elementTypeMapping">The type mapping for elements, if known.</param>
/// <param name="keyOrIndex">If <see langword="true" />, then a special mapping for a key or index may be returned.</param>
/// <param name="unicode">Specifies Unicode or ANSI mapping, or <see langword="null" /> for default.</param>
/// <param name="size">Specifies a size for the mapping, or <see langword="null" /> for default.</param>
/// <param name="rowVersion">Specifies a row-version, or <see langword="null" /> for default.</param>
/// <param name="precision">Specifies a precision for the mapping, or <see langword="null" /> for default.</param>
/// <param name="scale">Specifies a scale for the mapping, or <see langword="null" /> for default.</param>
/// <param name="key">If <see langword="true" />, then a special mapping for a key may be returned.</param>
public TypeMappingInfo(
Type? type = null,
CoreTypeMapping? elementTypeMapping = null,
Expand All @@ -235,11 +262,12 @@ public TypeMappingInfo(
int? size = null,
bool? rowVersion = null,
int? precision = null,
int? scale = null)
int? scale = null,
bool key = false)
{
ClrType = type?.UnwrapNullableType();
ElementTypeMapping = elementTypeMapping;

IsKey = key;
IsKeyOrIndex = keyOrIndex;
Size = size;
IsUnicode = unicode;
Expand All @@ -266,6 +294,7 @@ public TypeMappingInfo(
int? scale = null)
{
IsRowVersion = source.IsRowVersion;
IsKey = source.IsKey;
IsKeyOrIndex = source.IsKeyOrIndex;

var mappingHints = converter.MappingHints;
Expand Down Expand Up @@ -295,7 +324,12 @@ public TypeMappingInfo WithConverter(in ValueConverterInfo converterInfo)
=> new(this, converterInfo);

/// <summary>
/// Indicates whether or not the mapping is part of a key or index.
/// Indicates whether or not the mapping is part of a key or foreign key.
/// </summary>
public bool IsKey { get; init; }

/// <summary>
/// Indicates whether or not the mapping is part of a key, foreign key, or index.
/// </summary>
public bool IsKeyOrIndex { get; init; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,17 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
b.Property(e => e.IdUserState).HasDefaultValue(1).HasSentinel(667);
b.HasOne(e => e.UserState).WithMany(e => e.Users).HasForeignKey(e => e.IdUserState);
});

modelBuilder.Entity<StringKeyAndIndexParent>(
b =>
{
b.HasAlternateKey(e => e.AlternateId);
b.OwnsOne(
x => x.Child, b =>
{
b.WithOwner(e => e.Parent).HasForeignKey(e => e.ParentId);
});
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,134 @@ protected GraphUpdatesSqlServerTestBase(TFixture fixture)
{
}

[ConditionalFact] // Issue #32638
public virtual void Key_and_index_properties_use_appropriate_comparer()
{
var parent = new StringKeyAndIndexParent
{
Id = "Parent",
AlternateId = "Parent",
Index = "Index",
UniqueIndex = "UniqueIndex"
};

var child = new StringKeyAndIndexChild
{
Id = "Child",
ParentId = "parent"
};

using var context = CreateContext();
context.AttachRange(parent, child);

Assert.Same(child, parent.Child);
Assert.Same(parent, child.Parent);

parent.Id = "parent";
parent.AlternateId = "parent";
parent.Index = "index";
parent.UniqueIndex = "uniqueIndex";
child.Id = "child";
child.ParentId = "Parent";

context.ChangeTracker.DetectChanges();

var parentEntry = context.Entry(parent);
Assert.Equal(EntityState.Modified, parentEntry.State);
Assert.False(parentEntry.Property(e => e.Id).IsModified);
Assert.False(parentEntry.Property(e => e.AlternateId).IsModified);
Assert.True(parentEntry.Property(e => e.Index).IsModified);
Assert.True(parentEntry.Property(e => e.UniqueIndex).IsModified);

var childEntry = context.Entry(child);

if (childEntry.Metadata.IsOwned())
{
Assert.Equal(EntityState.Modified, childEntry.State);
Assert.True(childEntry.Property(e => e.Id).IsModified); // Not a key for the owned type
Assert.False(childEntry.Property(e => e.ParentId).IsModified);
}
else
{
Assert.Equal(EntityState.Unchanged, childEntry.State);
Assert.False(childEntry.Property(e => e.Id).IsModified);
Assert.False(childEntry.Property(e => e.ParentId).IsModified);
}

}

protected class StringKeyAndIndexParent : NotifyingEntity
{
private string _id;
private string _alternateId;
private string _uniqueIndex;
private string _index;
private StringKeyAndIndexChild _child;

public string Id
{
get => _id;
set => SetWithNotify(value, ref _id);
}

public string AlternateId
{
get => _alternateId;
set => SetWithNotify(value, ref _alternateId);
}

public string Index
{
get => _index;
set => SetWithNotify(value, ref _index);
}

public string UniqueIndex
{
get => _uniqueIndex;
set => SetWithNotify(value, ref _uniqueIndex);
}

public StringKeyAndIndexChild Child
{
get => _child;
set => SetWithNotify(value, ref _child);
}
}

protected class StringKeyAndIndexChild : NotifyingEntity
{
private string _id;
private string _parentId;
private int _foo;
private StringKeyAndIndexParent _parent;

public string Id
{
get => _id;
set => SetWithNotify(value, ref _id);
}

public string ParentId
{
get => _parentId;
set => SetWithNotify(value, ref _parentId);
}


public int Foo
{
get => _foo;
set => SetWithNotify(value, ref _foo);
}

public StringKeyAndIndexParent Parent
{
get => _parent;
set => SetWithNotify(value, ref _parent);
}
}

protected override IQueryable<Root> ModifyQueryRoot(IQueryable<Root> query)
=> query.AsSplitQuery();

Expand Down Expand Up @@ -59,6 +187,15 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con

modelBuilder.Entity<SomethingOfCategoryA>().Property<int>("CategoryId").HasDefaultValue(1);
modelBuilder.Entity<SomethingOfCategoryB>().Property(e => e.CategoryId).HasDefaultValue(2);

modelBuilder.Entity<StringKeyAndIndexParent>(
b =>
{
b.HasOne(e => e.Child)
.WithOne(e => e.Parent)
.HasForeignKey<StringKeyAndIndexChild>(e => e.ParentId)
.HasPrincipalKey<StringKeyAndIndexParent>(e => e.AlternateId);
});
}
}
}