|
3 | 3 |
|
4 | 4 | using Microsoft.EntityFrameworkCore.Internal; |
5 | 5 | using Microsoft.EntityFrameworkCore.Metadata.Internal; |
| 6 | +using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; |
6 | 7 | using Microsoft.EntityFrameworkCore.SqlServer.Design.Internal; |
7 | 8 | using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; |
8 | 9 | using Microsoft.Extensions.DependencyInjection.Extensions; |
@@ -2304,6 +2305,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) |
2304 | 2305 | { |
2305 | 2306 | j.HasKey("BlogsId", "PostsId"); |
2306 | 2307 | j.HasIndex(new[] { "PostsId" }, "IX_BlogPost_PostsId"); |
| 2308 | + j.IndexerProperty<int>("BlogsId"); |
| 2309 | + j.IndexerProperty<int>("PostsId"); |
2307 | 2310 | }); |
2308 | 2311 | }); |
2309 | 2312 |
|
@@ -2429,6 +2432,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) |
2429 | 2432 | { |
2430 | 2433 | j.HasKey("BlogsId", "PostsId"); |
2431 | 2434 | j.HasIndex(new[] { "PostsId" }, "IX_BlogPost_PostsId"); |
| 2435 | + j.IndexerProperty<int>("BlogsId"); |
| 2436 | + j.IndexerProperty<string>("PostsId"); |
2432 | 2437 | }); |
2433 | 2438 | }); |
2434 | 2439 |
|
@@ -2554,6 +2559,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) |
2554 | 2559 | { |
2555 | 2560 | j.HasKey("BlogsId", "PostsId"); |
2556 | 2561 | j.HasIndex(new[] { "PostsId" }, "IX_BlogPost_PostsId"); |
| 2562 | + j.IndexerProperty<int>("BlogsId"); |
| 2563 | + j.IndexerProperty<int>("PostsId"); |
2557 | 2564 | }); |
2558 | 2565 | }); |
2559 | 2566 |
|
@@ -2697,6 +2704,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) |
2697 | 2704 | { |
2698 | 2705 | j.HasKey("BlogsKey", "PostsId"); |
2699 | 2706 | j.HasIndex(new[] { "PostsId" }, "IX_BlogPost_PostsId"); |
| 2707 | + j.IndexerProperty<int>("BlogsKey"); |
| 2708 | + j.IndexerProperty<int>("PostsId"); |
2700 | 2709 | }); |
2701 | 2710 | }); |
2702 | 2711 |
|
@@ -2781,6 +2790,186 @@ public partial class Post |
2781 | 2790 | Assert.False(fk.PrincipalKey.IsPrimaryKey()); |
2782 | 2791 | }); |
2783 | 2792 |
|
| 2793 | + [ConditionalFact] |
| 2794 | + public Task Scaffold_skip_navigations_composite_fk() |
| 2795 | + { |
| 2796 | + var database = new DatabaseModel |
| 2797 | + { |
| 2798 | + Tables = |
| 2799 | + { |
| 2800 | + new DatabaseTable |
| 2801 | + { |
| 2802 | + Name = "AnnualValue", |
| 2803 | + Columns = |
| 2804 | + { |
| 2805 | + new DatabaseColumn { Name = "LearnAimRef", StoreType = "varchar(8)" }, |
| 2806 | + new DatabaseColumn { Name = "EffectiveFrom", StoreType = "date" } |
| 2807 | + }, |
| 2808 | + PrimaryKey = new DatabasePrimaryKey |
| 2809 | + { |
| 2810 | + Columns = { new DatabaseColumnRef("LearnAimRef"), new DatabaseColumnRef("EffectiveFrom") } |
| 2811 | + } |
| 2812 | + }, |
| 2813 | + new DatabaseTable |
| 2814 | + { |
| 2815 | + Name = "AcademicYear_Lookup", |
| 2816 | + Columns = |
| 2817 | + { |
| 2818 | + new DatabaseColumn { Name = "AcademicYear", StoreType = "varchar(4)" }, |
| 2819 | + new DatabaseColumn { Name = "AcademicYearDesc", StoreType = "varchar(150)", IsNullable = true }, |
| 2820 | + new DatabaseColumn { Name = "AcademicYearDesc2", StoreType = "varchar(100)", IsNullable = true } |
| 2821 | + }, |
| 2822 | + PrimaryKey = new DatabasePrimaryKey { Columns = { new DatabaseColumnRef("AcademicYear") } } |
| 2823 | + }, |
| 2824 | + new DatabaseTable |
| 2825 | + { |
| 2826 | + Name = "AnnualValue_AcademicYear_Mapping", |
| 2827 | + Columns = |
| 2828 | + { |
| 2829 | + new DatabaseColumn { Name = "AcademicYear", StoreType = "varchar(4)" }, |
| 2830 | + new DatabaseColumn { Name = "LearnAimRef", StoreType = "varchar(8)" }, |
| 2831 | + new DatabaseColumn { Name = "EffectiveFrom", StoreType = "date" } |
| 2832 | + }, |
| 2833 | + PrimaryKey = new DatabasePrimaryKey |
| 2834 | + { |
| 2835 | + Columns = |
| 2836 | + { |
| 2837 | + new DatabaseColumnRef("AcademicYear"), |
| 2838 | + new DatabaseColumnRef("LearnAimRef"), |
| 2839 | + new DatabaseColumnRef("EffectiveFrom") |
| 2840 | + } |
| 2841 | + }, |
| 2842 | + ForeignKeys = |
| 2843 | + { |
| 2844 | + new DatabaseForeignKey |
| 2845 | + { |
| 2846 | + Columns = { new DatabaseColumnRef("LearnAimRef"), new DatabaseColumnRef("EffectiveFrom") }, |
| 2847 | + PrincipalColumns = { new DatabaseColumnRef("LearnAimRef"), new DatabaseColumnRef("EffectiveFrom") }, |
| 2848 | + PrincipalTable = new DatabaseTableRef("AnnualValue"), |
| 2849 | + OnDelete = ReferentialAction.Cascade |
| 2850 | + }, |
| 2851 | + new DatabaseForeignKey |
| 2852 | + { |
| 2853 | + Columns = { new DatabaseColumnRef("AcademicYear") }, |
| 2854 | + PrincipalColumns = { new DatabaseColumnRef("AcademicYear") }, |
| 2855 | + PrincipalTable = new DatabaseTableRef("AcademicYear_Lookup"), |
| 2856 | + OnDelete = ReferentialAction.Cascade |
| 2857 | + } |
| 2858 | + } |
| 2859 | + } |
| 2860 | + } |
| 2861 | + }; |
| 2862 | + |
| 2863 | + return TestAsync( |
| 2864 | + serviceProvider => |
| 2865 | + { |
| 2866 | + foreach (var table in database.Tables) |
| 2867 | + { |
| 2868 | + table.Database = database; |
| 2869 | + foreach (var column in table.Columns) |
| 2870 | + { |
| 2871 | + column.Table = table; |
| 2872 | + } |
| 2873 | + |
| 2874 | + if (table.PrimaryKey != null) |
| 2875 | + { |
| 2876 | + table.PrimaryKey.Table = table; |
| 2877 | + FixupColumns(table, table.PrimaryKey.Columns); |
| 2878 | + } |
| 2879 | + |
| 2880 | + foreach (var fk in table.ForeignKeys) |
| 2881 | + { |
| 2882 | + fk.Table = table; |
| 2883 | + FixupColumns(table, fk.Columns); |
| 2884 | + |
| 2885 | + if (fk.PrincipalTable is DatabaseTableRef tableRef) |
| 2886 | + { |
| 2887 | + fk.PrincipalTable = database.Tables |
| 2888 | + .First(t => t.Name == tableRef.Name && t.Schema == tableRef.Schema); |
| 2889 | + } |
| 2890 | + |
| 2891 | + FixupColumns(fk.PrincipalTable, fk.PrincipalColumns); |
| 2892 | + } |
| 2893 | + } |
| 2894 | + |
| 2895 | + return serviceProvider.GetRequiredService<IScaffoldingModelFactory>().Create( |
| 2896 | + database, new ModelReverseEngineerOptions()); |
| 2897 | + |
| 2898 | + static void FixupColumns(DatabaseTable table, IList<DatabaseColumn> columns) |
| 2899 | + { |
| 2900 | + for (var i = 0; i < columns.Count; i++) |
| 2901 | + { |
| 2902 | + if (columns[i] is DatabaseColumnRef columnRef) |
| 2903 | + { |
| 2904 | + columns[i] = table.Columns.First(c => c.Name == columnRef.Name); |
| 2905 | + } |
| 2906 | + } |
| 2907 | + } |
| 2908 | + }, |
| 2909 | + new ModelCodeGenerationOptions { UseDataAnnotations = false, UseNullableReferenceTypes = true }, |
| 2910 | + code => |
| 2911 | + { |
| 2912 | + // Context has DbSets for the two principal entities (join table is hidden) |
| 2913 | + Assert.Contains("DbSet<AcademicYearLookup>", code.ContextFile.Code); |
| 2914 | + Assert.Contains("DbSet<AnnualValue>", code.ContextFile.Code); |
| 2915 | + |
| 2916 | + // Context wires up the many-to-many via UsingEntity |
| 2917 | + Assert.Contains("UsingEntity", code.ContextFile.Code); |
| 2918 | + Assert.Contains("AnnualValueAcademicYearMapping", code.ContextFile.Code); |
| 2919 | + Assert.Contains("IndexerProperty<DateOnly>(\"EffectiveFrom\")", code.ContextFile.Code); |
| 2920 | + |
| 2921 | + // Two entity files generated (join table is Dictionary<string,object>, no class file) |
| 2922 | + Assert.Equal(2, code.AdditionalFiles.Count); |
| 2923 | + |
| 2924 | + var lookupFile = code.AdditionalFiles.Single(f => f.Path == "AcademicYearLookup.cs"); |
| 2925 | + Assert.Contains("string AcademicYear", lookupFile.Code); |
| 2926 | + Assert.Contains("string? AcademicYearDesc", lookupFile.Code); |
| 2927 | + Assert.Contains("string? AcademicYearDesc2", lookupFile.Code); |
| 2928 | + Assert.Contains("ICollection<AnnualValue> AnnualValues", lookupFile.Code); |
| 2929 | + |
| 2930 | + var annualValueFile = code.AdditionalFiles.Single(f => f.Path == "AnnualValue.cs"); |
| 2931 | + Assert.Contains("string LearnAimRef", annualValueFile.Code); |
| 2932 | + Assert.Contains("DateOnly EffectiveFrom", annualValueFile.Code); |
| 2933 | + Assert.Contains("ICollection<AcademicYearLookup> AcademicYears", annualValueFile.Code); |
| 2934 | + }, |
| 2935 | + model => |
| 2936 | + { |
| 2937 | + var lookupType = model.FindEntityType("TestNamespace.AcademicYearLookup"); |
| 2938 | + Assert.NotNull(lookupType); |
| 2939 | + Assert.Collection( |
| 2940 | + lookupType.GetProperties().Select(p => p.Name).OrderBy(n => n), |
| 2941 | + p => Assert.Equal("AcademicYear", p), |
| 2942 | + p => Assert.Equal("AcademicYearDesc", p), |
| 2943 | + p => Assert.Equal("AcademicYearDesc2", p)); |
| 2944 | + Assert.Empty(lookupType.GetNavigations()); |
| 2945 | + var lookupSkipNav = Assert.Single(lookupType.GetSkipNavigations()); |
| 2946 | + Assert.Equal("AnnualValues", lookupSkipNav.Name); |
| 2947 | + |
| 2948 | + var annualValueType = model.FindEntityType("TestNamespace.AnnualValue"); |
| 2949 | + Assert.NotNull(annualValueType); |
| 2950 | + Assert.Collection( |
| 2951 | + annualValueType.GetProperties().Select(p => p.Name).OrderBy(n => n), |
| 2952 | + p => Assert.Equal("EffectiveFrom", p), |
| 2953 | + p => Assert.Equal("LearnAimRef", p)); |
| 2954 | + Assert.Empty(annualValueType.GetNavigations()); |
| 2955 | + var annualValueSkipNav = Assert.Single(annualValueType.GetSkipNavigations()); |
| 2956 | + Assert.Equal("AcademicYears", annualValueSkipNav.Name); |
| 2957 | + |
| 2958 | + Assert.Equal(lookupSkipNav, annualValueSkipNav.Inverse); |
| 2959 | + Assert.Equal(annualValueSkipNav, lookupSkipNav.Inverse); |
| 2960 | + |
| 2961 | + var joinEntityType = lookupSkipNav.ForeignKey.DeclaringEntityType; |
| 2962 | + Assert.Equal("AnnualValueAcademicYearMapping", joinEntityType.Name); |
| 2963 | + Assert.Equal(typeof(Dictionary<string, object>), joinEntityType.ClrType); |
| 2964 | + Assert.Equal(2, joinEntityType.GetForeignKeys().Count()); |
| 2965 | + Assert.Collection( |
| 2966 | + joinEntityType.GetProperties().Select(p => p.Name).OrderBy(n => n), |
| 2967 | + p => Assert.Equal("AcademicYear", p), |
| 2968 | + p => Assert.Equal("EffectiveFrom", p), |
| 2969 | + p => Assert.Equal("LearnAimRef", p)); |
| 2970 | + }); |
| 2971 | + } |
| 2972 | + |
2784 | 2973 | [ConditionalFact] |
2785 | 2974 | public Task Many_to_many_ef6() |
2786 | 2975 | => TestAsync( |
|
0 commit comments