Digamos que tengo las siguientes entidades:
public class Parent
{
public int Id { get; set; }
}
public class Child
{
public int Id { get; set; }
public int ParentId { get; set; }
}
¿Cuál es la primera sintaxis de API fluida del código para hacer cumplir que ParentId se crea en la base de datos con una restricción de clave externa a la tabla Padres, sin la necesidad de tener una propiedad de navegación ?
Sé que si agrego una propiedad de navegación de padre a hijo, puedo hacer esto:
modelBuilder.Entity<Child>()
.HasRequired<Parent>(c => c.Parent)
.WithMany()
.HasForeignKey(c => c.ParentId);
Pero no quiero la propiedad de navegación en este caso particular.
entity-framework
ef-code-first
RationalGeek
fuente
fuente
Respuestas:
Con EF Code First Fluent API es imposible. Siempre necesita al menos una propiedad de navegación para crear una restricción de clave externa en la base de datos.
Si está utilizando Code First Migrations, tiene la opción de agregar una nueva migración basada en código en la consola del administrador de paquetes (
add-migration SomeNewSchemaName
). Si cambió algo con su modelo o mapeo, se agregará una nueva migración. Si no cambió nada, fuerce una nueva migración utilizandoadd-migration -IgnoreChanges SomeNewSchemaName
. La migración solo contendrá métodos vacíosUp
yDown
en este caso.Luego puede modificar el
Up
método agregando lo siguiente:public override void Up() { // other stuff... AddForeignKey("ChildTableName", "ParentId", "ParentTableName", "Id", cascadeDelete: true); // or false CreateIndex("ChildTableName", "ParentId"); // if you want an index }
Al ejecutar esta migración (
update-database
en la consola de administración de paquetes) se ejecutará una declaración SQL similar 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])
Alternativamente, sin migraciones, puede ejecutar un comando SQL puro usando
context.Database.ExecuteSqlCommand(sql);
donde
context
es una instancia de su clase de contexto derivada ysql
es solo el comando SQL anterior como cadena.Tenga en cuenta que con todo esto EF no tiene ni idea de que
ParentId
es una clave externa que describe una relación. EF lo considerará solo como una propiedad escalar ordinaria. De alguna manera, todo lo anterior es solo una forma más complicada y lenta en comparación con simplemente abrir una herramienta de administración de SQL y agregar la restricción a mano.fuente
[ForeignKey("ParentTableName")]
. Eso vinculará la propiedad a cualquier clave que esté en la tabla principal. Sin embargo, ahora tiene un nombre de tabla codificado.Aunque esta publicación es para
Entity Framework
noEntity Framework Core
, podría ser útil para alguien que quiera lograr lo mismo usando Entity Framework Core (estoy usando V1.1.2).No necesito propiedades de navegación (a pesar de que es Niza), porque estoy practicando DDD y quiero
Parent
yChild
que hay dos raíces agregados separados. Quiero que puedan comunicarse entre sí a través de una clave externa, no a través deEntity Framework
propiedades de navegación específicas de la infraestructura .Todo lo que tiene que hacer es configurar la relación en un lado usando
HasOne
yWithMany
sin especificar las propiedades de navegación (después de todo, no están ahí).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"); }); ...... } }
También estoy dando ejemplos sobre cómo configurar las propiedades de la entidad, pero el más importante aquí es
HasOne<>
,WithMany()
yHasForeignKey()
.Espero eso ayude.
fuente
HasOne<Parent>()
y.WithMany()
en la configuración secundaria. No hacen referencia a las propiedades de navegación en absoluto, ya que de todos modos no hay propiedades de navegación definidas. Intentaré aclararlo con mis actualizaciones.Pequeña sugerencia para aquellos que desean usar DataAnotations y no desean exponer la propiedad de navegación: 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; } }
Eso es todo: se creará la clave externa con
cascade:true
afterAdd-Migration
.fuente
Parent
o simplementeParentId
?virtual
propiedades de @MarcWittke no pueden serprivate
.En el caso de EF Core , no es necesario proporcionar una propiedad de navegación. Simplemente puede proporcionar una clave externa en un lado de la relación. Un ejemplo simple con Fluent API:
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; } } }
fuente
Estoy usando .Net Core 3.1, EntityFramework 3.1.3. He estado buscando y la solución que se me ocurrió fue usar la versión genérica de
HasForeginKey<DependantEntityType>(e => e.ForeginKeyProperty)
. puedes crear una relación uno a uno así:builder.entity<Parent>() .HasOne<Child>() .WithOne<>() .HasForeginKey<Child>(c => c.ParentId); builder.entity<Child>() .Property(c => c.ParentId).IsRequired();
Espero que esto ayude o al menos proporcione algunas otras ideas sobre cómo usar el
HasForeginKey
método.fuente
Mi razón para no usar las propiedades de navegación son las dependencias de clase. Separé mis modelos en pocos ensamblajes, que se pueden usar o no en diferentes proyectos en cualquier combinación. Entonces, si tengo una entidad que tiene propiedad de nagivación para clasificar de otro ensamblado, necesito hacer referencia a ese ensamblado, que quiero evitar (o cualquier proyecto que use parte de ese modelo de datos completo lo llevará todo).
Y tengo una aplicación de migración separada, que se usa para migraciones (yo uso automigraciones) y creación de base de datos inicial. Este proyecto hace referencia a todo por razones obvias.
La solución es de estilo C:
alt
clave en VS)#if _MIGRATION
Contact
clase en el ejemplo).Muestra:
public int? ContactId { get; set; } #if _MIGRATION [ForeignKey(nameof(ContactId))] public Contact Contact { get; set; } #endif
Por supuesto, debería deshabilitar de la misma manera
using
directiva de la y cambiar el espacio de nombres.Después de eso, todos los consumidores pueden usar esa propiedad como un campo de base de datos habitual (y no hacer referencia a ensamblajes adicionales si no son necesarios), pero el servidor de base de datos sabrá que es FK y puede usar la conexión en cascada. Solución muy sucia. Pero funciona.
fuente