|
8 | 8 | using System.Linq;
|
9 | 9 | using Microsoft.EntityFrameworkCore.Diagnostics;
|
10 | 10 | using Microsoft.EntityFrameworkCore.Infrastructure;
|
| 11 | +using Microsoft.EntityFrameworkCore.Metadata.Builders; |
11 | 12 | using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
12 | 13 | using Microsoft.EntityFrameworkCore.Storage;
|
13 | 14 | using Xunit;
|
@@ -2753,6 +2754,269 @@ protected internal override void OnConfiguring(DbContextOptionsBuilder optionsBu
|
2753 | 2754 | public DbSet<Level2> Level2s { get; set; }
|
2754 | 2755 | }
|
2755 | 2756 |
|
| 2757 | + [ConditionalFact] |
| 2758 | + public void Use_correct_entity_after_SetValues() |
| 2759 | + { |
| 2760 | + var detachedProduct = new ProductX |
| 2761 | + { |
| 2762 | + Description = "Heavy Engine XT3" |
| 2763 | + }; |
| 2764 | + |
| 2765 | + var detachedRoom = new ContainerRoomX |
| 2766 | + { |
| 2767 | + Number = 1, |
| 2768 | + Product = detachedProduct |
| 2769 | + }; |
| 2770 | + |
| 2771 | + var detachedContainer = new ContainerX |
| 2772 | + { |
| 2773 | + Name = "C1", |
| 2774 | + Rooms = |
| 2775 | + { |
| 2776 | + detachedRoom |
| 2777 | + } |
| 2778 | + }; |
| 2779 | + |
| 2780 | + using (var context = new EscapeRoom(nameof(EscapeRoom))) |
| 2781 | + { |
| 2782 | + context.Add(detachedContainer); |
| 2783 | + context.SaveChanges(); |
| 2784 | + } |
| 2785 | + |
| 2786 | + using (var context = new EscapeRoom(nameof(EscapeRoom))) |
| 2787 | + { |
| 2788 | + var attachedProduct = new ProductX |
| 2789 | + { |
| 2790 | + Id = detachedProduct.Id, |
| 2791 | + Description = "Heavy Engine XT3" |
| 2792 | + }; |
| 2793 | + |
| 2794 | + var attachedRoom = new ContainerRoomX |
| 2795 | + { |
| 2796 | + Id = detachedRoom.Id, |
| 2797 | + ContainerId = detachedRoom.ContainerId, |
| 2798 | + Number = 1, |
| 2799 | + ProductId = detachedRoom.ProductId, |
| 2800 | + Product = attachedProduct |
| 2801 | + }; |
| 2802 | + |
| 2803 | + var attached = new ContainerX |
| 2804 | + { |
| 2805 | + Id = detachedContainer.Id, |
| 2806 | + Name = "C1", |
| 2807 | + Rooms = |
| 2808 | + { |
| 2809 | + attachedRoom |
| 2810 | + } |
| 2811 | + }; |
| 2812 | + |
| 2813 | + context.Attach(attached); |
| 2814 | + |
| 2815 | + detachedRoom.Product = null; |
| 2816 | + detachedRoom.ProductId = null; |
| 2817 | + |
| 2818 | + context.Entry(attachedRoom).CurrentValues.SetValues(detachedRoom); |
| 2819 | + |
| 2820 | + context.SaveChanges(); |
| 2821 | + |
| 2822 | + // Fails - see #16546 |
| 2823 | + //Assert.Equal(EntityState.Unchanged, context.Entry(attachedRoom).State); |
| 2824 | + } |
| 2825 | + } |
| 2826 | + |
| 2827 | + public class ContainerX |
| 2828 | + { |
| 2829 | + public int Id { get; set; } |
| 2830 | + public string Name { get; set; } |
| 2831 | + public List<ContainerRoomX> Rooms { get; set; } = new List<ContainerRoomX>(); |
| 2832 | + } |
| 2833 | + |
| 2834 | + public class ContainerRoomX |
| 2835 | + { |
| 2836 | + public int Id { get; set; } |
| 2837 | + public int Number { get; set; } |
| 2838 | + public int ContainerId { get; set; } |
| 2839 | + public ContainerX Container { get; set; } |
| 2840 | + public int? ProductId { get; set; } |
| 2841 | + public ProductX Product { get; set; } |
| 2842 | + } |
| 2843 | + |
| 2844 | + public class ProductX |
| 2845 | + { |
| 2846 | + public int Id { get; set; } |
| 2847 | + public string Description { get; set; } |
| 2848 | + public List<ContainerRoomX> Rooms { get; set; } = new List<ContainerRoomX>(); |
| 2849 | + } |
| 2850 | + |
| 2851 | + protected class EscapeRoom : DbContext |
| 2852 | + { |
| 2853 | + private readonly string _databaseName; |
| 2854 | + |
| 2855 | + public EscapeRoom(string databaseName) |
| 2856 | + { |
| 2857 | + _databaseName = databaseName; |
| 2858 | + } |
| 2859 | + |
| 2860 | + protected internal override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) |
| 2861 | + => optionsBuilder.UseInMemoryDatabase(_databaseName); |
| 2862 | + |
| 2863 | + protected internal override void OnModelCreating(ModelBuilder modelBuilder) |
| 2864 | + { |
| 2865 | + modelBuilder.Entity<ContainerRoomX>() |
| 2866 | + .HasOne(room => room.Product) |
| 2867 | + .WithMany(product => product.Rooms) |
| 2868 | + .HasForeignKey(room => room.ProductId) |
| 2869 | + .IsRequired(false) |
| 2870 | + .OnDelete(DeleteBehavior.Cascade); |
| 2871 | + } |
| 2872 | + } |
| 2873 | + |
| 2874 | + [ConditionalFact] |
| 2875 | + public void Replaced_duplicate_entities_are_used_even_with_bad_hash() |
| 2876 | + { |
| 2877 | + using (var context = new BadHashDay("BadHashDay")) |
| 2878 | + { |
| 2879 | + context.AddRange( |
| 2880 | + new ParentX |
| 2881 | + { |
| 2882 | + Id = 101, Name = "Parent1" |
| 2883 | + }, |
| 2884 | + new ChildX |
| 2885 | + { |
| 2886 | + Id = 201, Name = "Child1" |
| 2887 | + }, |
| 2888 | + new ParentChildX |
| 2889 | + { |
| 2890 | + ParentId = 101, ChildId = 201, SortOrder = 1 |
| 2891 | + }); |
| 2892 | + |
| 2893 | + context.SaveChanges(); |
| 2894 | + } |
| 2895 | + |
| 2896 | + using (var context = new BadHashDay("BadHashDay")) |
| 2897 | + { |
| 2898 | + var parent = context.Set<ParentX>().Single(x => x.Id == 101); |
| 2899 | + var join = context.Set<ParentChildX>().Single(); |
| 2900 | + |
| 2901 | + Assert.Equal(2, context.ChangeTracker.Entries().Count()); |
| 2902 | + Assert.Equal(EntityState.Unchanged, context.Entry(parent).State); |
| 2903 | + Assert.Equal(EntityState.Unchanged, context.Entry(join).State); |
| 2904 | + |
| 2905 | + parent.ParentChildren.Clear(); |
| 2906 | + |
| 2907 | + var newJoin = new ParentChildX |
| 2908 | + { |
| 2909 | + ParentId = 101, |
| 2910 | + ChildId = 201, |
| 2911 | + SortOrder = 1 |
| 2912 | + }; |
| 2913 | + |
| 2914 | + parent.ParentChildren = new List<ParentChildX> |
| 2915 | + { |
| 2916 | + newJoin |
| 2917 | + }; |
| 2918 | + |
| 2919 | + Assert.Equal(3, context.ChangeTracker.Entries().Count()); |
| 2920 | + Assert.Equal(EntityState.Unchanged, context.Entry(parent).State); |
| 2921 | + Assert.Equal(EntityState.Deleted, context.Entry(join).State); |
| 2922 | + Assert.Equal(EntityState.Added, context.Entry(newJoin).State); |
| 2923 | + |
| 2924 | + context.SaveChanges(); |
| 2925 | + |
| 2926 | + Assert.Equal(2, context.ChangeTracker.Entries().Count()); |
| 2927 | + Assert.Equal(EntityState.Unchanged, context.Entry(parent).State); |
| 2928 | + Assert.Equal(EntityState.Detached, context.Entry(join).State); |
| 2929 | + Assert.Equal(EntityState.Unchanged, context.Entry(newJoin).State); |
| 2930 | + } |
| 2931 | + } |
| 2932 | + |
| 2933 | + protected class ParentX |
| 2934 | + { |
| 2935 | + public int Id { get; set; } |
| 2936 | + public string Name { get; set; } |
| 2937 | + public virtual IList<ParentChildX> ParentChildren { get; set; } = new List<ParentChildX>(); |
| 2938 | + } |
| 2939 | + |
| 2940 | + protected class ParentChildX |
| 2941 | + { |
| 2942 | + public int ParentId { get; set; } |
| 2943 | + public int ChildId { get; set; } |
| 2944 | + public int SortOrder { get; set; } |
| 2945 | + public virtual ParentX Parent { get; set; } |
| 2946 | + public virtual ChildX Child { get; set; } |
| 2947 | + |
| 2948 | + // Bad implementation of Equals to test for regression |
| 2949 | + public override bool Equals(object obj) |
| 2950 | + { |
| 2951 | + if (obj == null) |
| 2952 | + { |
| 2953 | + return false; |
| 2954 | + } |
| 2955 | + |
| 2956 | + var other = (ParentChildX)obj; |
| 2957 | + |
| 2958 | + if (!Equals(ParentId, other.ParentId)) |
| 2959 | + { |
| 2960 | + return false; |
| 2961 | + } |
| 2962 | + |
| 2963 | + if (!Equals(ChildId, other.ChildId)) |
| 2964 | + { |
| 2965 | + return false; |
| 2966 | + } |
| 2967 | + |
| 2968 | + return true; |
| 2969 | + } |
| 2970 | + |
| 2971 | + // Bad implementation of GetHashCode to test for regression |
| 2972 | + public override int GetHashCode() |
| 2973 | + { |
| 2974 | + var hashCode = 13; |
| 2975 | + hashCode = (hashCode * 7) + ParentId.GetHashCode(); |
| 2976 | + hashCode = (hashCode * 7) + ChildId.GetHashCode(); |
| 2977 | + return hashCode; |
| 2978 | + } |
| 2979 | + } |
| 2980 | + |
| 2981 | + protected class ChildX |
| 2982 | + { |
| 2983 | + public int Id { get; set; } |
| 2984 | + public string Name { get; set; } |
| 2985 | + public virtual IList<ParentChildX> ParentChildren { get; set; } = new List<ParentChildX>(); |
| 2986 | + } |
| 2987 | + |
| 2988 | + protected class BadHashDay : DbContext |
| 2989 | + { |
| 2990 | + private readonly string _databaseName; |
| 2991 | + |
| 2992 | + public BadHashDay(string databaseName) |
| 2993 | + { |
| 2994 | + _databaseName = databaseName; |
| 2995 | + } |
| 2996 | + |
| 2997 | + protected internal override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) |
| 2998 | + => optionsBuilder.UseInMemoryDatabase(_databaseName); |
| 2999 | + |
| 3000 | + protected internal override void OnModelCreating(ModelBuilder modelBuilder) |
| 3001 | + { |
| 3002 | + modelBuilder.Entity<ParentX>() |
| 3003 | + .HasMany(x => x.ParentChildren) |
| 3004 | + .WithOne(op => op.Parent) |
| 3005 | + .IsRequired(); |
| 3006 | + |
| 3007 | + modelBuilder.Entity<ChildX>() |
| 3008 | + .HasMany(x => x.ParentChildren) |
| 3009 | + .WithOne(op => op.Child) |
| 3010 | + .IsRequired(); |
| 3011 | + |
| 3012 | + modelBuilder.Entity<ParentChildX>().HasKey( |
| 3013 | + x => new |
| 3014 | + { |
| 3015 | + x.ParentId, x.ChildId |
| 3016 | + }); |
| 3017 | + } |
| 3018 | + } |
| 3019 | + |
2756 | 3020 | [ConditionalFact]
|
2757 | 3021 | public void Detached_entity_is_not_replaced_by_tracked_entity()
|
2758 | 3022 | {
|
|
0 commit comments