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

Clarify that FK shadow properties are usually created by convention #1016

Closed
mattfrear opened this issue Oct 2, 2018 — with docs.microsoft.com · 12 comments · Fixed by #4128
Closed

Clarify that FK shadow properties are usually created by convention #1016

mattfrear opened this issue Oct 2, 2018 — with docs.microsoft.com · 12 comments · Fixed by #4128

Comments

Copy link

> They are most often used for foreign key properties

The Fluent API example on this page isn't much help. Can you provide an example that does something with relationships or foreign keys?


Document Details

Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

@ajcvickers
Copy link
Member

The fluent API docs for relationships and FKs is here: https://docs.microsoft.com/en-us/ef/core/modeling/relationships

@mattfrear
Copy link
Author

Yeah I know. My request still stands. There's a page about shadow properties, which says what they're most useful for, but then fails a decent example.

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .Property<DateTime>("LastUpdated");
    }

WTF does that line of code do? It's not obvious to me.

99% of people won't bother to provide feedback when they struggle and will turn to google to find an answer. So when someone does provide feedback it shouldn't be disregarded.

@ajcvickers
Copy link
Member

@mattfrear Trying to connect the dots here, maybe the confusion is that they are commonly used for FKs, but rarely explicitly defined for FKs. Hence showing API here to define an FK in shadow state would not be terribly useful since that's not a very common thing to do. Would it be clearer to say, "They are most often used for foreign key properties, where they are added to the model by convention when no foreign key property has been found by convention or configured explicitly."

With regard to not knowing what that line of code does, can you be more specific about what is ambiguous?

@mattfrear
Copy link
Author

mattfrear commented Oct 2, 2018

Would it be clearer to say, "They are most often used for foreign key properties, where they are added to the model by convention when no foreign key property has been found by convention or configured explicitly."

Yes, I think that would be an improvement.

With regard to not knowing what that line of code does, can you be more specific about what is ambiguous?

I have re-read the page and now it makes sense, so please disregard my earlier comment.

I landed on this page trying to solve a specific and frustrating problem (one to many relationship with a nullable int foreign key which should not be nullable), I was hoping this page would shed some light on my problem but I accept that I was perhaps looking in the wrong place. Sorry for being rude.

@ajcvickers ajcvickers reopened this Oct 2, 2018
@ajcvickers
Copy link
Member

@mattfrear Np
Re-opening because I think clarifying that FK shadow properties are usually created by convention would be useful.

@ajcvickers ajcvickers changed the title Please provide a more useful example Clarify that FK shadow properties are usually created by convention Oct 2, 2018
Copy link

anth-git commented Nov 2, 2018

I think that the example from https://docs.microsoft.com/en-us/ef/core/modeling/relationships#foreign-key should be used for the FluentApi paragraph.
It seems to be quite valid usecase to have FK that doesn't follow convention but we want to configure it as a shadow property. So it would be much useful example than example of just creating some arbitrary "LastUpdated" property, that doesn't have any meaning, especially that you stated by yourself that shadow properties are are most often used for foreign key.

        // Add the shadow property to the model
        modelBuilder.Entity<Post>()
            .Property<int>("BlogForeignKey");

        // Use the shadow property as a foreign key
        modelBuilder.Entity<Post>()
            .HasOne(p => p.Blog)
            .WithMany(b => b.Posts)
            .HasForeignKey("BlogForeignKey");

@divega divega added this to the 2.2.0 milestone Nov 10, 2018
@divega divega modified the milestones: 2.2.0, 3.0.0 Feb 21, 2019
Copy link

Yuant-tobing commented Mar 10, 2019

How to seed data to shadow properties ?

Continent model

public partial class Continent
{
    public string Code { get; set; }
    public string Name { get; set; }

    public ICollection<Country> Countries { get; set; }
}

Country model

    public partial class Country
    {
        public Country() => Provinces = new HashSet<Province>();

        public int Code { get; set; }
        public string IsoA2 { get; set; }
        public string IsoA3 { get; set; }
        public string Name { get; set; }
        public string Capital { get; set; }

        public Continent Continent { get; set; }
        public ICollection<Province> Provinces { get; set; }
    }

App context

            modelBuilder.Entity<Continent>(entity =>
            {
                entity.HasKey(e => e.Code)
                    .HasName("PK_Continent");

                entity.ToTable("continent");

                entity.Property(e => e.Code)
                    .HasColumnName("code")
                    .HasMaxLength(2)
                    .IsUnicode(false)
                    .IsFixedLength()
                    .ValueGeneratedNever();

                entity.Property(e => e.Name)
                    .IsRequired()
                    .HasColumnName("name")
                    .HasMaxLength(24)
                    .IsUnicode(false);
            });

            modelBuilder.Entity<Country>(entity =>
            {
                entity.ToTable("country");

                entity.HasKey(e => e.Code)
                    .HasName("PK_Country");

                entity.Property(e => e.Code)
                    .HasColumnName("code")
                    .ValueGeneratedNever();

                entity.Property(e => e.Capital)
                    .IsRequired(false)
                    .HasColumnName("capital")
                    .HasMaxLength(80)
                    .IsUnicode(false);

                entity.Property(e => e.IsoA2)
                    .IsRequired()
                    .HasColumnName("iso_a2")
                    .HasMaxLength(2)
                    .IsUnicode(false)
                    .IsFixedLength();

                entity.Property(e => e.IsoA3)
                    .IsRequired()
                    .HasColumnName("iso_a3")
                    .HasMaxLength(3)
                    .IsUnicode(false)
                    .IsFixedLength();

                entity.Property(e => e.Name)
                    .HasColumnName("name")
                    .HasMaxLength(80)
                    .IsUnicode(false);

                entity.Property<string>("continent_code")
                    .IsRequired()
                    .HasMaxLength(2)
                    .IsUnicode(false)
                    .IsFixedLength();

                entity.HasIndex("continent_code");

                entity.HasOne(d => d.Continent)
                    .WithMany(p => p.Countries)
                    .HasForeignKey("continent_code")
                    .HasConstraintName("FK_Country_Continent")
                    .OnDelete(DeleteBehavior.Cascade);
            });

App data seeder

        public static void Initialize(NusaportContext context)
        {
            context.Database.EnsureCreated();

            if (!context.Continents.Any())
            {
                var continents = new Continent[] {
                    new Continent { Code = "AF", Name = "Africa" },
                    .....
                };

                foreach (Continent c in continents)
                {
                    context.Continents.Add(c);
                }

                context.SaveChanges();
            }

            if (!context.Countries.Any())
            {
                var countries = new Country[] {
                    new Country { Code = 111, IsoA2 = "ZZ", IsoA3 = "ZZZ", Name = "Example", Capital = "Example" }
                };

                foreach (Country cn in countries)
                {
                    context.Countries.Add(cn);
                }

            return;
        }

I need to insert continent_code's value while seeding data to database,

@ajcvickers
Copy link
Member

@Yuant-tobing Use an anonymous type as shown in the docs.

@jzabroski
Copy link
Contributor

@ajcvickers One thing I've wished about MSFT Docs for years (going back to MSDN) was finer-grained breadcrumbs (# section anchors). In the Data Seeding docs, there is a hidden outline of user stories and one has to read through the web to untangle it all.

What would be hyper-text-awesome is if I could see all "owned types" related documentation across multiple pages all at once. Miguel De Icaza had a glimmer of such hopes with how Xamarin managed documentation (comments separate from code), but it feels like it never became a butterfly from its chrysalis.

It would probably also be nice if each example was a self-contained git repository, so that people could clone it in Github for Windows and just automatically play with it, fork it, submit bugs based on it.

Perhaps not very actionable given all the things you must do, but if I never ask, you will never know.

@jzabroski
Copy link
Contributor

@mattfrear I think the key concept is that FK shadow properties are primarily an artifact of domain-driven design. DDD enthusiasts do not want to expose keys in their domain model, because they have zero practical consequence to testing or to the business domain. All the keys do is connect a graph of objects together.

@anth-git

I think that the example from https://docs.microsoft.com/en-us/ef/core/modeling/relationships#foreign-key should be used for the FluentApi paragraph.
It seems to be quite valid usecase to have FK that doesn't follow convention but we want to configure it as a shadow property. So it would be much useful example than example of just creating some arbitrary "LastUpdated" property, that doesn't have any meaning, especially that you stated by yourself that shadow properties are are most often used for foreign key.

        // Add the shadow property to the model
        modelBuilder.Entity<Post>()
            .Property<int>("BlogForeignKey");

        // Use the shadow property as a foreign key
        modelBuilder.Entity<Post>()
            .HasOne(p => p.Blog)
            .WithMany(b => b.Posts)
            .HasForeignKey("BlogForeignKey");

I suspect this example is actually incorrect or only correct under certain conditions. If you are doing database-first with EFCore, you need to specify the type parameter, or the wrong overload of HasForeignKey will get selected by the .NET runtime. I say wrong loosely. The same problem happens in VisualStudio with IntelliSense because IntelliSense prefers auto-completing to the params string[] overload, rather than the generic type overload.

@AndriySvyryd
Copy link
Member

I suspect this example is actually incorrect or only correct under certain conditions. If you are doing database-first with EFCore, you need to specify the type parameter, or the wrong overload of HasForeignKey will get selected by the .NET runtime. I say wrong loosely. The same problem happens in VisualStudio with IntelliSense because IntelliSense prefers auto-completing to the params string[] overload, rather than the generic type overload.

@jzabroski That issue is only present when calling .HasOne().WithOne() because EF needs to know on which type to create the FK properties. For .HasOne().WithMany() the dependent side is unambiguous, so .HasForeignKey() doesn't have a type parameter.

@pha3z
Copy link

pha3z commented Oct 25, 2020

Can someone please explain this line of documentation?

"We recommend explicitly adding the shadow property to the model before using it as a foreign key (as shown below)."

It's on this page: https://docs.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key#shadow-foreign-key

It's extremely frustrating, because I am trying to reduce volume of code. There's no explanation of what would happen if you call the .HasForeignKey(""BlogForeignKey") method without having explicitly defined the Shadow Property beforehand. How do I know whether or not I can omit the explicit property addition?

UPDATE: After understanding the problem better, I opened a separate issue about this:
#2807

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants