Skip to content

Commit

Permalink
Add ValueGenerated.OnUpdate
Browse files Browse the repository at this point in the history
Second part of issue #7913

This change adds a new ValueGenerated option for requiring a value on insert, but then using the value from the store for subsequent updates. This is a binary breaking change, but not a functional breaking change after a re-compile.
  • Loading branch information
ajcvickers committed May 23, 2017
1 parent af24095 commit 08b0462
Show file tree
Hide file tree
Showing 23 changed files with 398 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,9 @@ protected virtual void GenerateProperty(
.Append(
property.ValueGenerated == ValueGenerated.OnAdd
? ".ValueGeneratedOnAdd()"
: ".ValueGeneratedOnAddOrUpdate()");
: property.ValueGenerated == ValueGenerated.OnUpdate
? ".ValueGeneratedOnUpdate()"
: ".ValueGeneratedOnAddOrUpdate()");
}

GeneratePropertyAnnotations(property, stringBuilder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,10 @@ public virtual void AddValueGeneratedConfiguration(
methodName = nameof(PropertyBuilder.ValueGeneratedOnAdd);
break;

case ValueGenerated.OnUpdate:
methodName = nameof(PropertyBuilder.ValueGeneratedOnUpdate);
break;

case ValueGenerated.OnAddOrUpdate:
methodName = nameof(PropertyBuilder.ValueGeneratedOnAddOrUpdate);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,11 @@ protected virtual PropertyBuilder VisitColumn([NotNull] EntityTypeBuilder builde
property.ValueGeneratedOnAdd();
}

if (column.ValueGenerated == ValueGenerated.OnUpdate)
{
property.ValueGeneratedOnUpdate();
}

if (column.ValueGenerated == ValueGenerated.OnAddOrUpdate)
{
property.ValueGeneratedOnAddOrUpdate();
Expand Down
96 changes: 95 additions & 1 deletion src/EFCore.Specification.Tests/StoreGeneratedTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ protected StoreGeneratedTestBase(TFixture fixture)
[InlineData("OnAddOrUpdateThrowBeforeUseAfter")]
[InlineData("OnAddOrUpdateThrowBeforeIgnoreAfter")]
[InlineData("OnAddOrUpdateThrowBeforeThrowAfter")]
[InlineData("OnUpdateThrowBeforeUseAfter")]
[InlineData("OnUpdateThrowBeforeIgnoreAfter")]
[InlineData("OnUpdateThrowBeforeThrowAfter")]
public virtual void Before_save_throw_always_throws_if_value_set(string propertyName)
{
ExecuteWithStrategyInTransaction(
Expand All @@ -55,6 +58,9 @@ public virtual void Before_save_throw_always_throws_if_value_set(string property
[InlineData("OnAddOrUpdateThrowBeforeUseAfter", "Rabbit")]
[InlineData("OnAddOrUpdateThrowBeforeIgnoreAfter", "Rabbit")]
[InlineData("OnAddOrUpdateThrowBeforeThrowAfter", "Rabbit")]
[InlineData("OnUpdateThrowBeforeUseAfter", "Rabbit")]
[InlineData("OnUpdateThrowBeforeIgnoreAfter", "Rabbit")]
[InlineData("OnUpdateThrowBeforeThrowAfter", "Rabbit")]
public virtual void Before_save_throw_ignores_value_if_not_set(string propertyName, string expectedValue)
{
var id = 0;
Expand All @@ -73,6 +79,7 @@ public virtual void Before_save_throw_ignores_value_if_not_set(string propertyNa
[Theory]
[InlineData("Never")]
[InlineData("OnAdd")]
[InlineData("OnUpdate")]
[InlineData("NeverUseBeforeUseAfter")]
[InlineData("NeverUseBeforeIgnoreAfter")]
[InlineData("NeverUseBeforeThrowAfter")]
Expand All @@ -82,6 +89,9 @@ public virtual void Before_save_throw_ignores_value_if_not_set(string propertyNa
[InlineData("OnAddOrUpdateUseBeforeUseAfter")]
[InlineData("OnAddOrUpdateUseBeforeIgnoreAfter")]
[InlineData("OnAddOrUpdateUseBeforeThrowAfter")]
[InlineData("OnUpdateUseBeforeUseAfter")]
[InlineData("OnUpdateUseBeforeIgnoreAfter")]
[InlineData("OnUpdateUseBeforeThrowAfter")]
public virtual void Before_save_use_always_uses_value_if_set(string propertyName)
{
var id = 0;
Expand All @@ -100,6 +110,7 @@ public virtual void Before_save_use_always_uses_value_if_set(string propertyName
[Theory]
[InlineData("Never", null)]
[InlineData("OnAdd", "Rabbit")]
[InlineData("OnUpdate", null)]
[InlineData("NeverUseBeforeUseAfter", null)]
[InlineData("NeverUseBeforeIgnoreAfter", null)]
[InlineData("NeverUseBeforeThrowAfter", null)]
Expand All @@ -109,6 +120,9 @@ public virtual void Before_save_use_always_uses_value_if_set(string propertyName
[InlineData("OnAddOrUpdateUseBeforeUseAfter", "Rabbit")]
[InlineData("OnAddOrUpdateUseBeforeIgnoreAfter", "Rabbit")]
[InlineData("OnAddOrUpdateUseBeforeThrowAfter", "Rabbit")]
[InlineData("OnUpdateUseBeforeUseAfter", null)]
[InlineData("OnUpdateUseBeforeIgnoreAfter", null)]
[InlineData("OnUpdateUseBeforeThrowAfter", null)]
public virtual void Before_save_use_ignores_value_if_not_set(string propertyName, string expectedValue)
{
var id = 0;
Expand All @@ -135,6 +149,9 @@ public virtual void Before_save_use_ignores_value_if_not_set(string propertyName
[InlineData("OnAddOrUpdateIgnoreBeforeUseAfter", "Rabbit")]
[InlineData("OnAddOrUpdateIgnoreBeforeIgnoreAfter", "Rabbit")]
[InlineData("OnAddOrUpdateIgnoreBeforeThrowAfter", "Rabbit")]
[InlineData("OnUpdateIgnoreBeforeUseAfter", "Rabbit")]
[InlineData("OnUpdateIgnoreBeforeIgnoreAfter", "Rabbit")]
[InlineData("OnUpdateIgnoreBeforeThrowAfter", "Rabbit")]
public virtual void Before_save_ignore_ignores_value_if_not_set(string propertyName, string expectedValue)
{
var id = 0;
Expand All @@ -161,6 +178,9 @@ public virtual void Before_save_ignore_ignores_value_if_not_set(string propertyN
[InlineData("OnAddOrUpdateIgnoreBeforeUseAfter", "Rabbit")]
[InlineData("OnAddOrUpdateIgnoreBeforeIgnoreAfter", "Rabbit")]
[InlineData("OnAddOrUpdateIgnoreBeforeThrowAfter", "Rabbit")]
[InlineData("OnUpdateIgnoreBeforeUseAfter", "Rabbit")]
[InlineData("OnUpdateIgnoreBeforeIgnoreAfter", "Rabbit")]
[InlineData("OnUpdateIgnoreBeforeThrowAfter", "Rabbit")]
public virtual void Before_save_ignore_ignores_value_even_if_set(string propertyName, string expectedValue)
{
var id = 0;
Expand All @@ -186,6 +206,9 @@ public virtual void Before_save_ignore_ignores_value_even_if_set(string property
[InlineData("OnAddOrUpdateUseBeforeThrowAfter")]
[InlineData("OnAddOrUpdateIgnoreBeforeThrowAfter")]
[InlineData("OnAddOrUpdateThrowBeforeThrowAfter")]
[InlineData("OnUpdateUseBeforeThrowAfter")]
[InlineData("OnUpdateIgnoreBeforeThrowAfter")]
[InlineData("OnUpdateThrowBeforeThrowAfter")]
public virtual void After_save_throw_always_throws_if_value_modified(string propertyName)
{
ExecuteWithStrategyInTransaction(
Expand All @@ -209,6 +232,9 @@ public virtual void After_save_throw_always_throws_if_value_modified(string prop
[InlineData("OnAddOrUpdateUseBeforeThrowAfter", "Rabbit")]
[InlineData("OnAddOrUpdateIgnoreBeforeThrowAfter", "Rabbit")]
[InlineData("OnAddOrUpdateThrowBeforeThrowAfter", "Rabbit")]
[InlineData("OnUpdateUseBeforeThrowAfter", null)]
[InlineData("OnUpdateIgnoreBeforeThrowAfter", "Rabbit")]
[InlineData("OnUpdateThrowBeforeThrowAfter", "Rabbit")]
public virtual void After_save_throw_ignores_value_if_not_modified(string propertyName, string expectedValue)
{
var id = 0;
Expand All @@ -230,11 +256,15 @@ public virtual void After_save_throw_ignores_value_if_not_modified(string proper

context.SaveChanges();
},
context => { Assert.Equal(expectedValue, GetValue(context.Anaises.Find(id), propertyName)); });
context =>
{
Assert.Equal(expectedValue, GetValue(context.Anaises.Find(id), propertyName));
});
}

[Theory]
[InlineData("OnAddOrUpdate", "Rabbit")]
[InlineData("OnUpdate", null)]
[InlineData("NeverUseBeforeIgnoreAfter", null)]
[InlineData("NeverIgnoreBeforeIgnoreAfter", null)]
[InlineData("NeverThrowBeforeIgnoreAfter", null)]
Expand All @@ -244,6 +274,9 @@ public virtual void After_save_throw_ignores_value_if_not_modified(string proper
[InlineData("OnAddOrUpdateUseBeforeIgnoreAfter", "Rabbit")]
[InlineData("OnAddOrUpdateIgnoreBeforeIgnoreAfter", "Rabbit")]
[InlineData("OnAddOrUpdateThrowBeforeIgnoreAfter", "Rabbit")]
[InlineData("OnUpdateUseBeforeIgnoreAfter", null)]
[InlineData("OnUpdateIgnoreBeforeIgnoreAfter", "Rabbit")]
[InlineData("OnUpdateThrowBeforeIgnoreAfter", "Rabbit")]
public virtual void After_save_ignore_ignores_value_if_not_modified(string propertyName, string expectedValue)
{
var id = 0;
Expand All @@ -270,6 +303,7 @@ public virtual void After_save_ignore_ignores_value_if_not_modified(string prope

[Theory]
[InlineData("OnAddOrUpdate", "Rabbit")]
[InlineData("OnUpdate", null)]
[InlineData("NeverUseBeforeIgnoreAfter", null)]
[InlineData("NeverIgnoreBeforeIgnoreAfter", null)]
[InlineData("NeverThrowBeforeIgnoreAfter", null)]
Expand All @@ -279,6 +313,9 @@ public virtual void After_save_ignore_ignores_value_if_not_modified(string prope
[InlineData("OnAddOrUpdateUseBeforeIgnoreAfter", "Rabbit")]
[InlineData("OnAddOrUpdateIgnoreBeforeIgnoreAfter", "Rabbit")]
[InlineData("OnAddOrUpdateThrowBeforeIgnoreAfter", "Rabbit")]
[InlineData("OnUpdateUseBeforeIgnoreAfter", null)]
[InlineData("OnUpdateIgnoreBeforeIgnoreAfter", "Rabbit")]
[InlineData("OnUpdateThrowBeforeIgnoreAfter", "Rabbit")]
public virtual void After_save_ignore_ignores_value_even_if_modified(string propertyName, string expectedValue)
{
var id = 0;
Expand Down Expand Up @@ -306,6 +343,8 @@ public virtual void After_save_ignore_ignores_value_even_if_modified(string prop
[Theory]
[InlineData("Never", null)]
[InlineData("OnAdd", "Rabbit")]
[InlineData("OnAddOrUpdate", "Rabbit")]
[InlineData("OnUpdate", null)]
[InlineData("NeverUseBeforeUseAfter", null)]
[InlineData("NeverIgnoreBeforeUseAfter", null)]
[InlineData("NeverThrowBeforeUseAfter", null)]
Expand All @@ -315,6 +354,9 @@ public virtual void After_save_ignore_ignores_value_even_if_modified(string prop
[InlineData("OnAddOrUpdateUseBeforeUseAfter", "Rabbit")]
[InlineData("OnAddOrUpdateIgnoreBeforeUseAfter", "Rabbit")]
[InlineData("OnAddOrUpdateThrowBeforeUseAfter", "Rabbit")]
[InlineData("OnUpdateUseBeforeUseAfter", null)]
[InlineData("OnUpdateIgnoreBeforeUseAfter", "Rabbit")]
[InlineData("OnUpdateThrowBeforeUseAfter", "Rabbit")]
public virtual void After_save_use_ignores_value_if_not_modified(string propertyName, string expectedValue)
{
var id = 0;
Expand Down Expand Up @@ -351,6 +393,9 @@ public virtual void After_save_use_ignores_value_if_not_modified(string property
[InlineData("OnAddOrUpdateUseBeforeUseAfter", "Daisy")]
[InlineData("OnAddOrUpdateIgnoreBeforeUseAfter", "Daisy")]
[InlineData("OnAddOrUpdateThrowBeforeUseAfter", "Daisy")]
[InlineData("OnUpdateUseBeforeUseAfter", "Daisy")]
[InlineData("OnUpdateIgnoreBeforeUseAfter", "Daisy")]
[InlineData("OnUpdateThrowBeforeUseAfter", "Daisy")]
public virtual void After_save_use_uses_value_if_modified(string propertyName, string expectedValue)
{
var id = 0;
Expand Down Expand Up @@ -1094,6 +1139,17 @@ protected class Anais
public string OnAddOrUpdateUseBeforeThrowAfter { get; set; }
public string OnAddOrUpdateIgnoreBeforeThrowAfter { get; set; }
public string OnAddOrUpdateThrowBeforeThrowAfter { get; set; }

public string OnUpdate { get; set; }
public string OnUpdateUseBeforeUseAfter { get; set; }
public string OnUpdateIgnoreBeforeUseAfter { get; set; }
public string OnUpdateThrowBeforeUseAfter { get; set; }
public string OnUpdateUseBeforeIgnoreAfter { get; set; }
public string OnUpdateIgnoreBeforeIgnoreAfter { get; set; }
public string OnUpdateThrowBeforeIgnoreAfter { get; set; }
public string OnUpdateUseBeforeThrowAfter { get; set; }
public string OnUpdateIgnoreBeforeThrowAfter { get; set; }
public string OnUpdateThrowBeforeThrowAfter { get; set; }
}

protected class StoreGeneratedContext : DbContext
Expand Down Expand Up @@ -1319,6 +1375,44 @@ protected virtual void OnModelCreating(ModelBuilder modelBuilder)
property = b.Property(e => e.OnAddOrUpdateThrowBeforeThrowAfter).ValueGeneratedOnAddOrUpdate().Metadata;
property.BeforeSaveBehavior = PropertyValueBehavior.Throw;
property.AfterSaveBehavior = PropertyValueBehavior.Throw;

b.Property(e => e.OnUpdate).ValueGeneratedOnUpdate();

property = b.Property(e => e.OnUpdateUseBeforeUseAfter).ValueGeneratedOnUpdate().Metadata;
property.BeforeSaveBehavior = PropertyValueBehavior.UseValue;
property.AfterSaveBehavior = PropertyValueBehavior.UseValue;

property = b.Property(e => e.OnUpdateIgnoreBeforeUseAfter).ValueGeneratedOnUpdate().Metadata;
property.BeforeSaveBehavior = PropertyValueBehavior.Ignore;
property.AfterSaveBehavior = PropertyValueBehavior.UseValue;

property = b.Property(e => e.OnUpdateThrowBeforeUseAfter).ValueGeneratedOnUpdate().Metadata;
property.BeforeSaveBehavior = PropertyValueBehavior.Throw;
property.AfterSaveBehavior = PropertyValueBehavior.UseValue;

property = b.Property(e => e.OnUpdateUseBeforeIgnoreAfter).ValueGeneratedOnUpdate().Metadata;
property.BeforeSaveBehavior = PropertyValueBehavior.UseValue;
property.AfterSaveBehavior = PropertyValueBehavior.Ignore;

property = b.Property(e => e.OnUpdateIgnoreBeforeIgnoreAfter).ValueGeneratedOnUpdate().Metadata;
property.BeforeSaveBehavior = PropertyValueBehavior.Ignore;
property.AfterSaveBehavior = PropertyValueBehavior.Ignore;

property = b.Property(e => e.OnUpdateThrowBeforeIgnoreAfter).ValueGeneratedOnUpdate().Metadata;
property.BeforeSaveBehavior = PropertyValueBehavior.Throw;
property.AfterSaveBehavior = PropertyValueBehavior.Ignore;

property = b.Property(e => e.OnUpdateUseBeforeThrowAfter).ValueGeneratedOnUpdate().Metadata;
property.BeforeSaveBehavior = PropertyValueBehavior.UseValue;
property.AfterSaveBehavior = PropertyValueBehavior.Throw;

property = b.Property(e => e.OnUpdateIgnoreBeforeThrowAfter).ValueGeneratedOnUpdate().Metadata;
property.BeforeSaveBehavior = PropertyValueBehavior.Ignore;
property.AfterSaveBehavior = PropertyValueBehavior.Throw;

property = b.Property(e => e.OnUpdateThrowBeforeThrowAfter).ValueGeneratedOnUpdate().Metadata;
property.BeforeSaveBehavior = PropertyValueBehavior.Throw;
property.AfterSaveBehavior = PropertyValueBehavior.Throw;
});
}
}
Expand Down
18 changes: 9 additions & 9 deletions src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -955,15 +955,15 @@ public virtual void DiscardStoreGeneratedValues()
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual bool IsStoreGenerated(IProperty property)
=> property.ValueGenerated != ValueGenerated.Never
&& ((EntityState == EntityState.Added
&& (property.BeforeSaveBehavior == PropertyValueBehavior.Ignore
|| HasTemporaryValue(property)
|| HasDefaultValue(property)))
|| (property.ValueGenerated == ValueGenerated.OnAddOrUpdate
&& EntityState == EntityState.Modified
&& (property.AfterSaveBehavior == PropertyValueBehavior.Ignore
|| !IsModified(property))));
=> (property.ValueGenerated.ForAdd() &&
EntityState == EntityState.Added
&& (property.BeforeSaveBehavior == PropertyValueBehavior.Ignore
|| HasTemporaryValue(property)
|| HasDefaultValue(property)))
|| (property.ValueGenerated.ForUpdate()
&& EntityState == EntityState.Modified
&& (property.AfterSaveBehavior == PropertyValueBehavior.Ignore
|| !IsModified(property)));

private bool HasDefaultValue(IProperty property)
=> property.ClrType.IsDefaultValue(this[property]);
Expand Down
15 changes: 12 additions & 3 deletions src/EFCore/Metadata/Builders/PropertyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,7 @@ public virtual PropertyBuilder ValueGeneratedOnAdd()
}

/// <summary>
/// Configures a property to have a value generated when saving a new or existing entity, unless
/// a non-null, non-temporary value has been set for a new entity, or the existing property value has
/// been modified for an existing entity, in which case the set value will be saved instead.
/// Configures a property to have a value generated when saving a new or existing entity.
/// </summary>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public virtual PropertyBuilder ValueGeneratedOnAddOrUpdate()
Expand All @@ -269,6 +267,17 @@ public virtual PropertyBuilder ValueGeneratedOnAddOrUpdate()
return this;
}

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

return this;
}

/// <summary>
/// <para>
/// Sets the backing field to use for this property.
Expand Down
11 changes: 8 additions & 3 deletions src/EFCore/Metadata/Builders/PropertyBuilder`.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,14 +189,19 @@ public PropertyBuilder([NotNull] InternalPropertyBuilder builder)
=> (PropertyBuilder<TProperty>)base.ValueGeneratedOnAdd();

/// <summary>
/// Configures a property to have a value generated only when saving a new or existing entity, unless
/// a non-null, non-temporary value has been set for a new entity, or the existing property value has
/// been modified for an existing entity, in which case the set value will be saved instead.
/// Configures a property to have a value generated when saving a new or existing entity.
/// </summary>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public new virtual PropertyBuilder<TProperty> ValueGeneratedOnAddOrUpdate()
=> (PropertyBuilder<TProperty>)base.ValueGeneratedOnAddOrUpdate();

/// <summary>
/// Configures a property to have a value generated when saving an existing entity.
/// </summary>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public new virtual PropertyBuilder<TProperty> ValueGeneratedOnUpdate()
=> (PropertyBuilder<TProperty>)base.ValueGeneratedOnUpdate();

/// <summary>
/// <para>
/// Sets the backing field to use for this property.
Expand Down
Loading

0 comments on commit 08b0462

Please sign in to comment.