Unfortunately currently (as of EF Core 2.0.1) this behavior is not controllable. I guess it's supposed to be improvement from EF6 since it allows you to perform identity inserts.
Fortunately EF Core is build on service based architecture which allows you ( (although not so easily) to replace almost every aspect. In this case, the responsible service is called IValueGenerationManager
and is implemented by ValueGenerationManager
class. The lines that provide the aforementioned behavior are
private static IEnumerable<IProperty> FindPropagatingProperties(InternalEntityEntry entry)
=> entry.EntityType.GetProperties().Where(
property => property.IsForeignKey()
&& property.ClrType.IsDefaultValue(entry[property]));
private static IEnumerable<IProperty> FindGeneratingProperties(InternalEntityEntry entry)
=> entry.EntityType.GetProperties().Where(
property => property.RequiresValueGenerator()
&& property.ClrType.IsDefaultValue(entry[property]));
specifically the && property.ClrType.IsDefaultValue(entry[property])
condition.
It would have been nice if these methods were virtual
, but they are not, so in order to remove that check you basically need to copy almost all code.
Add the following code to a new code file in your project:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Threading;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.ValueGeneration;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
namespace Microsoft.EntityFrameworkCore
{
public static class Extensions
{
public static DbContextOptionsBuilder UseEF6CompatibleValueGeneration(this DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.ReplaceService<IValueGenerationManager, EF6CompatibleValueGeneratorManager>();
return optionsBuilder;
}
}
}
namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal
{
public class EF6CompatibleValueGeneratorManager : ValueGenerationManager
{
private readonly IValueGeneratorSelector valueGeneratorSelector;
private readonly IKeyPropagator keyPropagator;
public EF6CompatibleValueGeneratorManager(IValueGeneratorSelector valueGeneratorSelector, IKeyPropagator keyPropagator)
: base(valueGeneratorSelector, keyPropagator)
{
this.valueGeneratorSelector = valueGeneratorSelector;
this.keyPropagator = keyPropagator;
}
public override InternalEntityEntry Propagate(InternalEntityEntry entry)
{
InternalEntityEntry chosenPrincipal = null;
foreach (var property in FindPropagatingProperties(entry))
{
var principalEntry = keyPropagator.PropagateValue(entry, property);
if (chosenPrincipal == null)
chosenPrincipal = principalEntry;
}
return chosenPrincipal;
}
public override void Generate(InternalEntityEntry entry)
{
var entityEntry = new EntityEntry(entry);
foreach (var property in FindGeneratingProperties(entry))
{
var valueGenerator = GetValueGenerator(entry, property);
SetGeneratedValue(entry, property, valueGenerator.Next(entityEntry), valueGenerator.GeneratesTemporaryValues);
}
}
public override async Task GenerateAsync(InternalEntityEntry entry, CancellationToken cancellationToken = default(CancellationToken))
{
var entityEntry = new EntityEntry(entry);
foreach (var property in FindGeneratingProperties(entry))
{
var valueGenerator = GetValueGenerator(entry, property);
SetGeneratedValue(entry, property, await valueGenerator.NextAsync(entityEntry, cancellationToken), valueGenerator.GeneratesTemporaryValues);
}
}
static IEnumerable<IProperty> FindPropagatingProperties(InternalEntityEntry entry)
{
return entry.EntityType.GetProperties().Where(property => property.IsForeignKey());
}
static IEnumerable<IProperty> FindGeneratingProperties(InternalEntityEntry entry)
{
return entry.EntityType.GetProperties().Where(property => property.RequiresValueGenerator());
}
ValueGenerator GetValueGenerator(InternalEntityEntry entry, IProperty property)
{
return valueGeneratorSelector.Select(property, property.IsKey() ? property.DeclaringEntityType : entry.EntityType);
}
static void SetGeneratedValue(InternalEntityEntry entry, IProperty property, object generatedValue, bool isTemporary)
{
if (generatedValue == null) return;
entry[property] = generatedValue;
if (isTemporary)
entry.MarkAsTemporary(property, true);
}
}
}
then override OnConfiguring
in your DbContext
derived class and add the following line:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// ...
optionsBuilder.UseEF6CompatibleValueGeneration();
}
and that's it - problem solved.