web-dev-qa-db-fra.com

Code EF Première clé étrangère sans propriété de navigation

Disons que j'ai les entités suivantes:

public class Parent
{
    public int Id { get; set; }
}
public class Child
{
    public int Id { get; set; }
    public int ParentId { get; set; }
}

Quelle est la première syntaxe d'API couramment utilisée pour appliquer que ParentId est créé dans la base de données avec une contrainte de clé étrangère dans la table Parents, sans avoir besoin d'avoir une propriété de navigation?

Je sais que si j'ajoute une propriété de navigation Parent à enfant, je peux le faire:

modelBuilder.Entity<Child>()
    .HasRequired<Parent>(c => c.Parent)
    .WithMany()
    .HasForeignKey(c => c.ParentId);

Mais je ne veux pas la propriété de navigation dans ce cas particulier.

57
RationalGeek

Avec l'API EF Code First Fluent, c'est impossible. Vous avez toujours besoin d'au moins une propriété de navigation pour créer une contrainte de clé étrangère dans la base de données.

Si vous utilisez Code First Migrations, vous avez la possibilité d'ajouter une nouvelle migration basée sur le code sur la console du gestionnaire de packages (add-migration SomeNewSchemaName). Si vous avez modifié quelque chose avec votre modèle ou mappé une nouvelle migration sera ajoutée. Si vous n'avez rien changé, forcez une nouvelle migration en utilisant add-migration -IgnoreChanges SomeNewSchemaName. Dans ce cas, la migration ne contiendra que les méthodes Up et Down vides.

Ensuite, vous pouvez modifier la méthode Up en y ajoutant le suivi:

public override void Up()
{
    // other stuff...

    AddForeignKey("ChildTableName", "ParentId", "ParentTableName", "Id",
        cascadeDelete: true); // or false
    CreateIndex("ChildTableName", "ParentId"); // if you want an index
}

Exécution de cette migration (update-database sur la console de gestion des packages) exécutera une instruction SQL similaire à celle-ci (pour SQL Server):

ALTER TABLE [ChildTableName] ADD CONSTRAINT [FK_SomeName]
FOREIGN KEY ([ParentId]) REFERENCES [ParentTableName] ([Id])

CREATE INDEX [IX_SomeName] ON [ChildTableName] ([ParentId])

Sinon, sans migrations, vous pouvez simplement exécuter une commande SQL pure en utilisant

context.Database.ExecuteSqlCommand(sql);

context est une instance de votre classe de contexte dérivée et sql est juste la commande SQL ci-dessus en tant que chaîne.

Sachez qu'avec tout cela, EF n'a aucune idée que ParentId est une clé étrangère qui décrit une relation. EF ne le considérera que comme une propriété scalaire ordinaire. D'une manière ou d'une autre, tout ce qui précède n'est qu'un moyen plus compliqué et plus lent que d'ouvrir simplement un outil de gestion SQL et d'ajouter la contrainte à la main.

55
Slauma

Bien que ce message soit destiné à Entity Framework Et non à Entity Framework Core, Il peut être utile pour quelqu'un qui souhaite réaliser la même chose en utilisant Entity Framework Core (j'utilise V1.1.2).

Je n'ai pas besoin de propriétés de navigation (bien qu'elles soient agréables) car je pratique le DDD et je veux que Parent et Child soient deux racines d'agrégat distinctes. Je veux qu'ils puissent se parler via une clé étrangère et non via les propriétés de navigation Entity Framework Spécifiques à l'infrastructure.

Tout ce que vous avez à faire est de configurer la relation d'un côté à l'aide de HasOne et WithMany sans spécifier les propriétés de navigation (elles ne sont pas là après tout).

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) {}

    protected override void OnModelCreating(ModelBuilder builder)
    {
        ......

        builder.Entity<Parent>(b => {
            b.HasKey(p => p.Id);
            b.ToTable("Parent");
        });

        builder.Entity<Child>(b => {
            b.HasKey(c => c.Id);
            b.Property(c => c.ParentId).IsRequired();

            // Without referencing navigation properties (they're not there anyway)
            b.HasOne<Parent>()    // <---
                .WithMany()       // <---
                .HasForeignKey(c => c.ParentId);

            // Just for comparison, with navigation properties defined,
            // (let's say you call it Parent in the Child class and Children
            // collection in Parent class), you might have to configure them 
            // like:
            // b.HasOne(c => c.Parent)
            //     .WithMany(p => p.Children)
            //     .HasForeignKey(c => c.ParentId);

            b.ToTable("Child");
        });

        ......
    }
}

Je donne également des exemples sur la façon de configurer les propriétés d'entité, mais le plus important ici est HasOne<>, WithMany() et HasForeignKey().

J'espère que ça aide.

63
David Liang

Petit conseil pour ceux qui veulent utiliser DataAnotations et ne veulent pas exposer la propriété de navigation - utilisez protected

public class Parent
{
    public int Id { get; set; }
}
public class Child
{
    public int Id { get; set; }
    public int ParentId { get; set; }

    protected virtual Parent Parent { get; set; }
}

C'est tout - la clé étrangère avec cascade:true Après Add-Migration Sera créée.

16
tenbits

Dans le cas d'EF Core, vous n'avez pas nécessairement besoin de fournir une propriété de navigation. Vous pouvez simplement fournir une clé étrangère d'un côté de la relation. Un exemple simple avec Fluent API:

    using Microsoft.EntityFrameworkCore;
    using System.Collections.Generic;

    namespace EFModeling.Configuring.FluentAPI.Samples.Relationships.NoNavigation
    {
        #region Model
        class MyContext : DbContext
        {
            public DbSet<Blog> Blogs { get; set; }
            public DbSet<Post> Posts { get; set; }

            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                 modelBuilder.Entity<Post>()
                    .HasOne<Blog>()
                    .WithMany()
                    .HasForeignKey(p => p.BlogId);
             }
        }

        public class Blog
        {
             public int BlogId { get; set; }
             public string Url { get; set; }
        }

        public class Post
        {
             public int PostId { get; set; }
             public string Title { get; set; }
             public string Content { get; set; }

            public int BlogId { get; set; }
        }
        #endregion
    }
2
walkerbox