Digamos que eu tenha as seguintes entidades:
public class Parent
{
public int Id { get; set; }
}
public class Child
{
public int Id { get; set; }
public int ParentId { get; set; }
}
Qual é a sintaxe de API fluente do código para garantir que ParentId seja criado no banco de dados com uma restrição de chave estrangeira para a tabela Pais, sem a necessidade de ter uma propriedade de navegação ?
Eu sei que se eu adicionar uma propriedade de navegação pai para filho, posso fazer o seguinte:
modelBuilder.Entity<Child>()
.HasRequired<Parent>(c => c.Parent)
.WithMany()
.HasForeignKey(c => c.ParentId);
Mas não quero a propriedade de navegação neste caso específico.
entity-framework
ef-code-first
RationalGeek
fonte
fonte
Respostas:
Com EF Code First Fluent API isso é impossível. Você sempre precisa de pelo menos uma propriedade de navegação para criar uma restrição de chave estrangeira no banco de dados.
Se você estiver usando Migrações Code First, terá a opção de adicionar uma nova migração baseada em código no console do gerenciador de pacotes (
add-migration SomeNewSchemaName
). Se você mudou algo com seu modelo ou mapeamento, uma nova migração será adicionada. Se você não alterou nada force uma nova migração usandoadd-migration -IgnoreChanges SomeNewSchemaName
. A migração conterá apenas métodosUp
e vaziosDown
neste caso.Depois, você pode modificar o
Up
método adicionando o seguinte a ele:public override void Up() { // other stuff... AddForeignKey("ChildTableName", "ParentId", "ParentTableName", "Id", cascadeDelete: true); // or false CreateIndex("ChildTableName", "ParentId"); // if you want an index }
Executar esta migração (
update-database
no console de gerenciamento de pacote) executará uma instrução SQL semelhante a esta (para SQL Server):ALTER TABLE [ChildTableName] ADD CONSTRAINT [FK_SomeName] FOREIGN KEY ([ParentId]) REFERENCES [ParentTableName] ([Id]) CREATE INDEX [IX_SomeName] ON [ChildTableName] ([ParentId])
Como alternativa, sem migrações, você pode simplesmente executar um comando SQL puro usando
context.Database.ExecuteSqlCommand(sql);
onde
context
é uma instância de sua classe de contexto derivada esql
é apenas o comando SQL acima como string.Esteja ciente de que com tudo isso EF não tem idéia de que
ParentId
é uma chave estrangeira que descreve um relacionamento. EF irá considerá-lo apenas como uma propriedade escalar comum. De alguma forma, tudo acima é apenas uma maneira mais complicada e lenta em comparação com apenas abrir uma ferramenta de gerenciamento de SQL e adicionar a restrição manualmente.fonte
[ForeignKey("ParentTableName")]
. Isso vinculará a propriedade a qualquer chave que esteja na tabela pai. Agora você tem um nome de tabela embutido no código.Embora este post seja para
Entity Framework
não serEntity Framework Core
, pode ser útil para alguém que deseja alcançar o mesmo usando o Entity Framework Core (estou usando a V1.1.2).Eu não preciso de propriedades de navegação (embora eles são agradáveis) porque eu estou praticando DDD e eu quero
Parent
eChild
ser duas raízes agregados separados. Quero que eles possam se comunicar por meio de uma chave estrangeira, não por meio deEntity Framework
propriedades de navegação específicas da infraestrutura .Tudo que você precisa fazer é configurar o relacionamento em um lado usando
HasOne
eWithMany
sem especificar as propriedades de navegação (elas não estão lá, afinal).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"); }); ...... } }
Estou dando exemplos de como configurar propriedades de entidade também, mas o mais importante aqui é
HasOne<>
,WithMany()
eHasForeignKey()
.Espero que ajude.
fonte
HasOne<Parent>()
e.WithMany()
no thie configuração criança. Eles não fazem referência às propriedades de navegação, já que não há propriedades de navegação definidas de qualquer maneira. Vou tentar deixar isso mais claro com minhas atualizações.Uma pequena dica para quem deseja usar DataAnotations e não deseja expor a propriedade de navegação - use
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; } }
É isso - a chave estrangeira com
cascade:true
depoisAdd-Migration
será criada.fonte
Parent
ou apenasParentId
?virtual
propriedades @MarcWittke não podem serprivate
.No caso do EF Core, você não precisa necessariamente fornecer uma propriedade de navegação. Você pode simplesmente fornecer uma chave estrangeira em um lado do relacionamento. Um exemplo simples com API Fluent:
using Microsoft.EntityFrameworkCore; using System.Collections.Generic; namespace EFModeling.Configuring.FluentAPI.Samples.Relationships.NoNavigation { 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; } } }
fonte
Estou usando .Net Core 3.1, EntityFramework 3.1.3. Tenho pesquisado por aí e a solução que encontrei estava usando a versão genérica
HasForeginKey<DependantEntityType>(e => e.ForeginKeyProperty)
. você pode criar uma relação um para um assim:builder.entity<Parent>() .HasOne<Child>() .WithOne<>() .HasForeginKey<Child>(c => c.ParentId); builder.entity<Child>() .Property(c => c.ParentId).IsRequired();
Espero que isso ajude ou pelo menos forneça algumas outras idéias sobre como usar o
HasForeginKey
método.fonte
Meu motivo para não usar as propriedades de navegação são as dependências de classe. Separei meus modelos em poucas montagens, que podem ser usadas ou não em diferentes projetos em quaisquer combinações. Portanto, se eu tiver uma entidade que tem propriedade de nagivação para a classe de outro assembly, preciso fazer referência a esse assembly, que desejo evitar (ou qualquer projeto que use parte desse modelo de dados completo carregará tudo com ele).
E eu tenho um aplicativo de migração separado, que é usado para migrações (eu uso automigrações) e criação inicial do banco de dados. Este projeto faz referência a tudo por motivos óbvios.
A solução é estilo C:
alt
chave no VS)#if _MIGRATION
Contact
classe no exemplo).Amostra:
public int? ContactId { get; set; } #if _MIGRATION [ForeignKey(nameof(ContactId))] public Contact Contact { get; set; } #endif
Claro que você deve da mesma forma desativar a
using
diretiva e alterar o namespace.Depois disso, todos os consumidores podem usar essa propriedade como campo de banco de dados normal (e não fazer referência a assemblies adicionais se eles não forem necessários), mas o servidor de banco de dados saberá que é FK e pode usar o cascateamento. Solução muito suja. Mas funciona.
fonte