Skip to content

Commit

Permalink
Add ValueGenerated.OnUpdateSometimes for shared columns
Browse files Browse the repository at this point in the history
Fixes #21139
  • Loading branch information
AndriySvyryd committed Aug 25, 2020
1 parent dc0cdcc commit a36f2e5
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,9 @@ protected virtual void GenerateProperty(
? ".ValueGeneratedOnAdd()"
: property.ValueGenerated == ValueGenerated.OnUpdate
? ".ValueGeneratedOnUpdate()"
: ".ValueGeneratedOnAddOrUpdate()");
: property.ValueGenerated == ValueGenerated.OnUpdateSometimes
? ".ValueGeneratedOnUpdateSometimes()"
: ".ValueGeneratedOnAddOrUpdate()");
}

GeneratePropertyAnnotations(property, stringBuilder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,19 @@ private static void TryUniquifyColumnNames(
if ((identifyingMemberInfo != null
&& identifyingMemberInfo.IsSameAs(otherProperty.PropertyInfo ?? (MemberInfo)otherProperty.FieldInfo))
|| (property.IsPrimaryKey() && otherProperty.IsPrimaryKey())
|| (property.IsConcurrencyToken && otherProperty.IsConcurrencyToken))
|| (property.IsConcurrencyToken && otherProperty.IsConcurrencyToken)
|| (!property.Builder.CanSetColumnName(null) && !otherProperty.Builder.CanSetColumnName(null)))
{
if (property.GetAfterSaveBehavior() == PropertySaveBehavior.Save
&& otherProperty.GetAfterSaveBehavior() == PropertySaveBehavior.Save
&& property.ValueGenerated == ValueGenerated.Never
&& otherProperty.ValueGenerated == ValueGenerated.Never)
{
// Handle this with a default value convention #9329
property.Builder.ValueGenerated(ValueGenerated.OnUpdateSometimes);
otherProperty.Builder.ValueGenerated(ValueGenerated.OnUpdateSometimes);
}

continue;
}

Expand Down Expand Up @@ -231,7 +242,11 @@ private static string TryUniquify(
}

columnName = Uniquifier.Uniquify(columnName, properties, maxLength);
property.Builder.HasColumnName(columnName);
if (property.Builder.HasColumnName(columnName) == null)
{
return null;
}

properties[columnName] = property;
return columnName;
}
Expand Down
14 changes: 7 additions & 7 deletions src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1056,14 +1056,14 @@ public object this[[NotNull] IPropertyBase propertyBase] // Intentionally non-vi

var equals = ValuesEqualFunc(property);

if (equals(value, defaultValue))
if (_storeGeneratedValues.TryGetValue(storeGeneratedIndex, out var generatedValue)
&& !equals(generatedValue, defaultValue))
{
if (_storeGeneratedValues.TryGetValue(storeGeneratedIndex, out var generatedValue)
&& !equals(generatedValue, defaultValue))
{
return generatedValue;
}
return generatedValue;
}

if (equals(value, defaultValue))
{
if (_temporaryValues.TryGetValue(storeGeneratedIndex, out generatedValue)
&& !equals(generatedValue, defaultValue))
{
Expand Down Expand Up @@ -1100,7 +1100,7 @@ private void SetProperty(
bool isCascadeDelete,
CurrentValueType valueType)
{
var currentValue = this[propertyBase];
var currentValue = ReadPropertyValue(propertyBase);

var asProperty = propertyBase as Property;
int propertyIndex;
Expand Down
11 changes: 11 additions & 0 deletions src/EFCore/Metadata/Builders/PropertyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,17 @@ public virtual PropertyBuilder ValueGeneratedOnUpdate()
return this;
}

/// <summary>
/// Configures a property to have a value generated under certain conditions when saving an existing entity.
/// </summary>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public virtual PropertyBuilder ValueGeneratedOnUpdateSometimes()
{
Builder.ValueGenerated(ValueGenerated.OnUpdateSometimes, ConfigurationSource.Explicit);

return this;
}

/// <summary>
/// <para>
/// Sets the backing field to use for this property.
Expand Down
3 changes: 1 addition & 2 deletions src/EFCore/Metadata/Internal/PropertyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,7 @@ public static bool RequiresValueGenerator([NotNull] this IProperty property)
/// </summary>
public static bool MayBeStoreGenerated([NotNull] this IProperty property)
{
if (property.ValueGenerated != ValueGenerated.Never
|| property.IsConcurrencyToken)
if (property.ValueGenerated != ValueGenerated.Never)
{
return true;
}
Expand Down
6 changes: 6 additions & 0 deletions src/EFCore/Metadata/ValueGenerated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ public enum ValueGenerated
/// </summary>
OnUpdate = 2,

/// <summary>
/// No value is generated when the entity is first added to the database, but a value will be read
/// from the database under certain conditions when the entity is subsequently updated.
/// </summary>
OnUpdateSometimes = 4,

/// <summary>
/// A value is read from the database when the entity is first added and whenever the entity
/// is subsequently updated. This is typically used for computed columns and scenarios such as
Expand Down
79 changes: 79 additions & 0 deletions test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1457,6 +1457,85 @@ public virtual void TableName_preserved_when_generic()
});
}

[ConditionalFact]
public virtual void Shared_columns_are_stored_in_the_snapshot()
{
Test(
builder =>
{
builder.Entity<EntityWithOneProperty>(b =>
{
b.ToTable("EntityWithProperties");
b.Property<int>("AlternateId").HasColumnName("AlternateId");
});
builder.Entity<EntityWithTwoProperties>(b =>
{
b.ToTable("EntityWithProperties");
b.Property<int>(e => e.AlternateId).HasColumnName("AlternateId");
b.HasOne(e => e.EntityWithOneProperty).WithOne(e => e.EntityWithTwoProperties)
.HasForeignKey<EntityWithTwoProperties>(e => e.Id);
});
},
AddBoilerPlate(
GetHeading()
+ @"
modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithOneProperty"", b =>
{
b.Property<int>(""Id"")
.ValueGeneratedOnAdd()
.HasColumnType(""int"")
.UseIdentityColumn();
b.Property<int>(""AlternateId"")
.ValueGeneratedOnUpdateSometimes()
.HasColumnType(""int"")
.HasColumnName(""AlternateId"");
b.HasKey(""Id"");
b.ToTable(""EntityWithProperties"");
});
modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithTwoProperties"", b =>
{
b.Property<int>(""Id"")
.ValueGeneratedOnAdd()
.HasColumnType(""int"")
.UseIdentityColumn();
b.Property<int>(""AlternateId"")
.ValueGeneratedOnUpdateSometimes()
.HasColumnType(""int"")
.HasColumnName(""AlternateId"");
b.HasKey(""Id"");
b.ToTable(""EntityWithProperties"");
});
modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithTwoProperties"", b =>
{
b.HasOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithOneProperty"", ""EntityWithOneProperty"")
.WithOne(""EntityWithTwoProperties"")
.HasForeignKey(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithTwoProperties"", ""Id"")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation(""EntityWithOneProperty"");
});
modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithOneProperty"", b =>
{
b.Navigation(""EntityWithTwoProperties"");
});", usingSystem: false),
model =>
{
var entityType = model.FindEntityType(typeof(EntityWithOneProperty));
Assert.Equal(ValueGenerated.OnUpdateSometimes, entityType.FindProperty("AlternateId").ValueGenerated);
});
}

[ConditionalFact]
public virtual void PrimaryKey_name_preserved_when_generic()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,17 @@ public virtual void Can_use_optional_dependents_with_shared_concurrency_tokens()
var scooter = context.Set<PoweredVehicle>().Include(v => v.Engine).Single(v => v.Name == "Electric scooter");

Assert.Equal(scooter.SeatingCapacity, context.Entry(scooter.Engine).Property<int>("SeatingCapacity").CurrentValue);

scooter.SeatingCapacity = 2;
context.SaveChanges();
}

using (var context = CreateContext())
{
var scooter = context.Set<PoweredVehicle>().Include(v => v.Engine).Single(v => v.Name == "Electric scooter");

Assert.Equal(2, scooter.SeatingCapacity);
Assert.Equal(2, context.Entry(scooter.Engine).Property<int>("SeatingCapacity").CurrentValue);
}
}
}
Expand Down

0 comments on commit a36f2e5

Please sign in to comment.