2

I'm in .net8 ef core with postgres database and code first approach.

I have these two class:

    public class Soggetto 
    {
        public long Id { get; set; }

        public virtual Azienda Azienda { get; set; }
        public long AziendaId { get; set; }
    }

    public class Azienda
    {
        public long Id { get; set; }

        public virtual Soggetto Soggetto { get; set; }
        public long SoggettoId { get; set; }
    }

and these two fluent configuration:

    public class SoggettoConfiguration : BaseAziendaAnagraficaEntityConfiguration<Soggetto>
    {
        public override void Configure(EntityTypeBuilder<Soggetto> builder)
        {
            base.Configure(builder);

            builder
                .HasOne(soggetto => soggetto.Azienda)
                .WithOne(azienda => azienda.Soggetto)
                .HasForeignKey<Soggetto>(x => x.AziendaId);
        }      
    }

    public class AziendaConfiguration : BaseEntityConfiguration<Azienda>
    {
        public override void Configure(EntityTypeBuilder<Azienda> builder)
        {
            base.Configure(builder);


            builder
                .HasOne(azienda => azienda.Soggetto)
                .WithOne(soggetto => soggetto.Azienda)
                .HasForeignKey<Azienda>(azienda => azienda.SoggettoId);
        }
    }

So when EF generate code for migration, I find foreign key to Soggetto in Azienda table and no foreign key to Azienda in Soggetto table. My expectation is to find both FK: SoggettoId in Azienda table and AziendaId in Soggetto table.

Any idea?

EDIT:

Clarification

Azienda is like a Tenant, every table has a TenantId to identify who is the owner of the record. Soggetto is like Subject, it could be person, delivery guy, client, supplier and a tenant... i mean something with Name, email, and other information.

So, every Soggetto (e.g.subject,person) has one Azienda (tenant) to identify the owner (eg. those are my clients). On the other way Azienda (tenant) has a name, email and other information stored in Soggetto.

4
  • No FKs in a one-to-one relationship. There's a FK in a one-to-zero-or-one relationship. Use the same PK in both tables. Commented Jan 31, 2024 at 14:55
  • @GHDevOps Could you please give me an example? Commented Jan 31, 2024 at 15:53
  • I think you're getting confused with the optional navigation property in Soggetto. Look here first with no virtual properties: learn.microsoft.com/en-us/ef/core/modeling/relationships/…. Commented Jan 31, 2024 at 16:50
  • The builder is optional, and I personally think it can get confusing. You always need a one side. Which ever entity is created first will never have a FK. Commented Jan 31, 2024 at 16:53

2 Answers 2

1

I guess that you have a small misunderstanding about one-to-one relationships (or I have misunderstood your question). Basically it is covered in the EF Core docs - One-to-one relationships:

A required relationship ensures that every dependent entity must be associated with some principal entity. However, a principal entity can always exist without any dependent entity. That is, a required relationship does not indicate that there will always be a dependent entity. There is no way in the EF model, and also no standard way in a relational database, to ensure that a principal is associated with a dependent. If this is needed, then it must be implemented in application (business) logic. See Required navigations for more information.

If you want to simulate the "truly" one-to-one relationship the only option via EF is to use owned entity types as mentioned in the linked previously "Required navigations" portion of the docs:

There is one exception to this rule - when the principal and dependent types are sharing the same table in a relational database, or contained in a document. This can happen with owned types, or non-owned types sharing the same table. In this case, the navigation property from the principal to the dependent can be marked as required, indicating that the dependent must exist.

If you have several one-to-one relationships between tables (i.e. Soggetto can have a depended Azienda and Azienda can have another depended Soggetto) then you will need to have several properties. For example something along these lines (not tested):

public class Soggetto 
{
    public long Id { get; set; }

    public virtual Azienda ChildAzienda { get; set; }

    public long? ParentAziendaId { get; set; }
    public virtual Azienda? ParentAzienda { get; set; }
}

public class Azienda 
{
    public long Id { get; set; }

    public long ParentSoggettoId { get; set; }
    public virtual Soggetto ParentSoggetto { get; set; }

    public virtual Soggetto ChildSoggetto { get; set; }
}

And map them correspondingly with the Fluent API. For example:

public class SoggettoConfiguration : IEntityTypeConfiguration<Soggetto>
{
    public void Configure(EntityTypeBuilder<Soggetto> builder)
    {
        builder
            .HasOne(soggetto => soggetto.ChildAzienda)
            .WithOne(azienda => azienda.ParentSoggetto)
            ;
    }      
}

public class AziendaConfiguration : IEntityTypeConfiguration<Azienda>
{
    public  void Configure(EntityTypeBuilder<Azienda> builder)
    {
        builder
            .HasOne(azienda => azienda.ChildSoggetto)
            .WithOne(soggetto => soggetto.ParentAzienda)
            ;
    }
}
Sign up to request clarification or add additional context in comments.

Comments

1

First, a clear explanation of the relationship is needed.

In your current model, an Azienda is required to create a Soggetto (since the id is not nullable).

Conversely, a Soggetto is required to create an Azienda.

This is a contradiction. Neither can be created first.

Edited

Given the clarification provided by the questioner, it appears that:

  • Azienda represents a Tenant.
  • Every Soggetto should be linked to an Azienda(Tenant) to denote ownership.

This translates to the following in terms of Entity Framework (EF) modeling:

If an Azienda can have multiple Soggettos:

public class Azienda
{
    public long Id { get; set; }
    public List<Soggetto> Soggettos { get; set; }
}
public class Soggetto
{
    public long Id { get; set; }

    public virtual Azienda Azienda { get; set; }
    public long AziendaId { get; set; }

    internal class Configuration : IEntityTypeConfiguration<Soggetto>
    {
        public void Configure(EntityTypeBuilder<Soggetto> builder)
        {
            builder
                .HasOne(o => o.Azienda)
                .WithMany(o => o.Soggettos);
        }
    }
}

Generated migration code:

migrationBuilder.CreateTable(
    name: "Azienda",
    columns: table => new
    {
        Id = table.Column<long>(type: "bigint", nullable: false)
            .Annotation("SqlServer:Identity", "1, 1")
    },
    constraints: table =>
    {
        table.PrimaryKey("PK_Azienda", x => x.Id);
    });

migrationBuilder.CreateTable(
    name: "Soggetto",
    columns: table => new
    {
        Id = table.Column<long>(type: "bigint", nullable: false)
            .Annotation("SqlServer:Identity", "1, 1"),
        AziendaId = table.Column<long>(type: "bigint", nullable: false)
    },
    constraints: table =>
    {
        table.PrimaryKey("PK_Soggetto", x => x.Id);
        table.ForeignKey(
            name: "FK_Soggetto_Azienda_AziendaId", 
            column: x => x.AziendaId,
            principalTable: "Azienda",
            principalColumn: "Id",
            onDelete: ReferentialAction.Cascade);
    });
migrationBuilder.CreateIndex(
    name: "IX_Soggetto_AziendaId",
    table: "Soggetto",
    column: "AziendaId");

If an Azienda can have only one Soggetto:

public class Azienda
{
    public long Id { get; set; }
    public Soggetto Soggetto { get; set; }
}
public class Soggetto
{
    public long Id { get; set; }

    public virtual Azienda Azienda { get; set; }
    public long AziendaId { get; set; }

    internal class Configuration : IEntityTypeConfiguration<Soggetto>
    {
        public void Configure(EntityTypeBuilder<Soggetto> builder)
        {
            builder
                .HasOne(o => o.Azienda)
                .WithOne(o => o.Soggetto);
        }
    }
}

Genereted migration code:

migrationBuilder.CreateTable(
    name: "Azienda",
    columns: table => new
    {
        Id = table.Column<long>(type: "bigint", nullable: false)
            .Annotation("SqlServer:Identity", "1, 1")
    },
    constraints: table =>
    {
        table.PrimaryKey("PK_Azienda", x => x.Id);
    });

migrationBuilder.CreateTable(
    name: "Soggetto",
    columns: table => new
    {
        Id = table.Column<long>(type: "bigint", nullable: false)
            .Annotation("SqlServer:Identity", "1, 1"),
        AziendaId = table.Column<long>(type: "bigint", nullable: false)
    },
    constraints: table =>
    {
        table.PrimaryKey("PK_Soggetto", x => x.Id);
        table.ForeignKey(
            name: "FK_Soggetto_Azienda_AziendaId",
            column: x => x.AziendaId,
            principalTable: "Azienda",
            principalColumn: "Id",
            onDelete: ReferentialAction.Cascade);
    });

migrationBuilder.CreateIndex(
    name: "IX_Soggetto_AziendaId",
    table: "Soggetto",
    column: "AziendaId",
    unique: true); // <- set foreign key unique.

You can simply create Soggeto like this:

var newItem = new Soggetto
{
    Azienda = new Azienda()
};
context.Soggetto.Add(newItem);
await context.SaveChangesAsync();

Or conversely (in the case of a 1:1 relationship):

var newItem = new Azienda
{
    Soggetto = new Soggetto()
};
context.Azienda.Add(newItem);
await context.SaveChangesAsync();

2 Comments

Azienda is like a Tenant, every table has a TenantId to identify who is the owner of the record. Soggetto is like Subject, it could be person, delivery guy, client, supplier and a tenant... i mean something with Name, email, and other information. So, every Soggetto (e.g.subject,person) has one Azienda (tenant) to identify the owner (eg. those are my clients). On the other way Azienda (tenant) has a name, email and other information stored in Soggetto.
"If an Azienda can have only one Soggetto" that's my case, but it is not creating FK to SoggettoId in Azienda table.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.