Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FK for generated key using TPC sometimes gets temporary value persisted to database on SaveChanges #28654

Closed
ajcvickers opened this issue Aug 10, 2022 · 2 comments · Fixed by #28851
Labels
area-save-changes closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Milestone

Comments

@ajcvickers
Copy link
Contributor

using (var context = new AnimalsContext())
{
    await context.Database.EnsureDeletedAsync();

    Console.WriteLine(context.Model.ToDebugString());
    Console.WriteLine();

    await context.Database.EnsureCreatedAsync();

    var catFood = new PetFood("Lily's Kitchen", LifeStage.Adult);
    var dogFood = new PetFood("Canagan", LifeStage.Adult);
    var hay = new FarmFood("Hay");
    var sushi = new HumanFood("Sushi", 670);

    var arthur = new Human("Arthur") { Food = sushi };
    var wendy = new Human("Wendy");
    var christi = new Human("Christi");

    var alice = new Cat("Alice", "MBA") { Vet = "Pengelly", Food = catFood, Humans = { arthur, wendy } };

    var mac = new Cat("Mac", "Preschool") { Vet = "Pengelly", Food = catFood, Humans = { arthur, wendy } };

    var toast = new Dog("Toast", "Mr. Squirrel") { Vet = "Pengelly", Food = dogFood, Humans = { arthur, wendy } };

    var clyde = new FarmAnimal("Clyde", "Equus africanus asinus") { Value = 100.0m, Food = hay };

    wendy.FavoriteAnimal = toast;
    arthur.FavoriteAnimal = alice;
    christi.FavoriteAnimal = clyde;

    await context.AddRangeAsync(wendy, arthur, christi, alice, mac, toast, clyde);
    await context.SaveChangesAsync();
}

Console.WriteLine();

using (var context = new AnimalsContext())
{
    foreach (var human in context.Humans)
    {
        Console.WriteLine($"Human.FavoriteAnimalId = {context.Entry(human).Property("FavoriteAnimalId").CurrentValue}");
    }
}

public abstract class Animal
{
    protected Animal(string name)
    {
        Name = name;
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public abstract string Species { get; }

    public Food? Food { get; set; }
}

public abstract class Pet : Animal
{
    protected Pet(string name)
        : base(name)
    {
    }

    public string? Vet { get; set; }

    public ICollection<Human> Humans { get; } = new List<Human>();
}

public class FarmAnimal : Animal
{
    public FarmAnimal(string name, string species)
        : base(name)
    {
        Species = species;
    }

    public override string Species { get; }

    [Precision(18, 2)]
    public decimal Value { get; set; }

    public override string ToString()
        => $"Farm animal '{Name}' ({Species}/{Id}) worth {Value:C} eats {Food?.ToString() ?? "<Unknown>"}";
}

public class Cat : Pet
{
    public Cat(string name, string educationLevel)
        : base(name)
    {
        EducationLevel = educationLevel;
    }

    public string EducationLevel { get; set; }

    public override string Species
        => "Felis catus";

    public override string ToString()
        => $"Cat '{Name}' ({Species}/{Id}) with education '{EducationLevel}' eats {Food?.ToString() ?? "<Unknown>"}";
}

public class Dog : Pet
{
    public Dog(string name, string favoriteToy)
        : base(name)
    {
        FavoriteToy = favoriteToy;
    }

    public string FavoriteToy { get; set; }

    public override string Species
        => "Canis familiaris";

    public override string ToString()
        => $"Dog '{Name}' ({Species}/{Id}) with favorite toy '{FavoriteToy}' eats {Food?.ToString() ?? "<Unknown>"}";
}

public class Human : Animal
{
    public Human(string name)
        : base(name)
    {
    }

    public override string Species
        => "Homo sapiens";

    public Animal? FavoriteAnimal { get; set; }
    public ICollection<Pet> Pets { get; } = new List<Pet>();

    public override string ToString()
        => $"Human '{Name}' ({Species}/{Id}) with favorite animal '{FavoriteAnimal?.Name ?? "<Unknown>"}'"
            + $" eats {Food?.ToString() ?? "<Unknown>"}";
}

public abstract class Food
{
    public Guid Id { get; set; }
}

public class PetFood : Food
{
    public PetFood(string brand, LifeStage lifeStage)
    {
        Brand = brand;
        LifeStage = lifeStage;
    }

    public string Brand { get; set; }
    public LifeStage LifeStage { get; set; }

    public override string ToString()
        => $"Pet food by '{Brand}' ({Id}) for life stage {LifeStage}";
}

public enum LifeStage
{
    Juvenile,
    Adult,
    Senior
}

public class HumanFood : Food
{
    public HumanFood(string name, int calories)
    {
        Name = name;
        Calories = calories;
    }

    [Column("Name")]
    public string Name { get; set; }

    public int Calories { get; set; }

    public override string ToString()
        => $"{Name} ({Id}) with calories {Calories}";
}

public class FarmFood : Food
{
    public FarmFood(string name)
    {
        Name = name;
    }

    [Column("Name")]
    public string Name { get; set; }

    public override string ToString()
        => $"{Name} ({Id})";
}

public class AnimalsContext : DbContext
{
    public DbSet<Animal> Animals
        => Set<Animal>();

    public DbSet<Pet> Pets
        => Set<Pet>();

    public DbSet<FarmAnimal> FarmAnimals
        => Set<FarmAnimal>();

    public DbSet<Cat> Cats
        => Set<Cat>();

    public DbSet<Dog> Dogs
        => Set<Dog>();

    public DbSet<Human> Humans
        => Set<Human>();

    public DbSet<Food> Foods
        => Set<Food>();

    public DbSet<PetFood> PetFoods
        => Set<PetFood>();

    public DbSet<FarmFood> FarmFoods
        => Set<FarmFood>();

    public DbSet<HumanFood> HumanFoods
        => Set<HumanFood>();

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .EnableSensitiveDataLogging()
            .UseSqlServer(@$"Server=(localdb)\mssqllocaldb;Database={GetType().Name}");

        optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Animal>().UseTpcMappingStrategy();
        modelBuilder.Entity<Food>().UseTpcMappingStrategy();

        modelBuilder.Entity<FarmAnimal>().Property(e => e.Species);

        modelBuilder.Entity<Human>()
            .HasMany(e => e.Pets)
            .WithMany(e => e.Humans)
            .UsingEntity<Dictionary<object, string>>(
                "PetsHumans",
                r => r.HasOne<Pet>().WithMany().OnDelete(DeleteBehavior.Cascade),
                l => l.HasOne<Human>().WithMany().OnDelete(DeleteBehavior.ClientCascade));
    }
}
Human.FavoriteAnimalId = -2147482646
Human.FavoriteAnimalId = -2147482644
Human.FavoriteAnimalId = -2147482642
@ajcvickers ajcvickers changed the title FK for generated key sometimes gets temporary value persisted to database on SaveChanges FK for generated key using TPC sometimes gets temporary value persisted to database on SaveChanges Aug 10, 2022
@ajcvickers ajcvickers added this to the 7.0.0 milestone Aug 11, 2022
@ajcvickers
Copy link
Contributor Author

Note: The What's New in EF7 doc will need updating when this issue is fixed.

AndriySvyryd added a commit that referenced this issue Aug 18, 2022
@AndriySvyryd
Copy link
Member

We now only break up batches for store FK constraints, but since the principal uses TPC we don't create one for it, so we end up sending the dependents in the same batch as the principals, before we get the store-generated PKs

@AndriySvyryd AndriySvyryd added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Aug 24, 2022
@AndriySvyryd AndriySvyryd removed their assignment Aug 24, 2022
@ajcvickers ajcvickers modified the milestones: 7.0.0, 7.0.0-rc2 Aug 31, 2022
@ajcvickers ajcvickers modified the milestones: 7.0.0-rc2, 7.0.0 Nov 5, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-save-changes closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants