EF Code First yabancı anahtar gezinme özelliği olmadan


91

Aşağıdaki varlıklara sahip olduğumu varsayalım:

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

Bir gezinti özelliğine ihtiyaç duyulmadan, ParentId'nin veritabanında Parents tablosuna yabancı anahtar kısıtlamasıyla oluşturulmasını zorunlu kılan kod ilk akıcı API sözdizimi nedir?

Üst Öğeye bir gezinme özelliği eklersem, şunu yapabileceğimi biliyorum:

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

Ancak bu özel durumda navigasyon özelliğini istemiyorum.


1
Bunun sadece EF ile mümkün olduğunu sanmıyorum, muhtemelen bunu ayarlamak için manuel geçişte biraz ham SQL kullanmanız gerekiyor
sevilmedi

@LukeMcGregor korktuğum şey buydu. Bunu bir cevap haline getirirseniz, doğru olduğunu varsayarak onu kabul etmekten memnuniyet duyarım. :-)
RationalGeek

Navigasyon özelliğine sahip olmamanın belirli bir nedeni var mı? Gezinme özelliğini sizin için özel kılmak ister - kuruluşun dışında görünmez, ancak EF'i memnun eder. (Not Bunu denemedim, ancak bunun işe yarayacağını düşünüyorum - özel mülklerin haritalanması hakkındaki bu gönderiye bir göz atın romiller.com/2012/10/01/… )
Pawel

6
İstemiyorum çünkü ihtiyacım yok. Sadece bir çerçevenin gereksinimlerini karşılamak için bir tasarıma fazladan gereksiz şeyler koymak zorunda kalmaktan hoşlanmıyorum. Navigasyon desteğini takmam beni öldürür mü? Hayır. Aslında şimdilik yaptığım şey bu.
RationalGeek

Bir ilişki kurmak için her zaman en az bir tarafta navigasyon özelliğine ihtiyacınız vardır. Daha fazla bilgi için stackoverflow.com/a/7105288/105445
Wahid Bitar

Yanıtlar:


63

EF Code First Fluent API ile bu imkansızdır. Veritabanında yabancı anahtar kısıtlaması oluşturmak için her zaman en az bir gezinti özelliğine ihtiyacınız vardır.

Code First Migrations kullanıyorsanız, paket yöneticisi konsolunda ( add-migration SomeNewSchemaName) yeni bir kod tabanlı geçiş ekleme seçeneğiniz vardır . Modelinizle veya eşlemenizle bir şey değiştirdiyseniz, yeni bir geçiş eklenecektir. Hiçbir şeyi değiştirmediyseniz kullanarak yeni bir geçişi zorlayın add-migration -IgnoreChanges SomeNewSchemaName. Taşıma, bu durumda yalnızca boş Upve Downyöntemleri içerecektir .

Ardından Up, aşağıdaki ifadeyi ekleyerek yöntemi değiştirebilirsiniz :

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

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

Bu geçişi çalıştırmak ( update-databasepaket yönetim konsolunda) şuna benzer bir SQL ifadesi çalıştırır (SQL Server için):

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

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

Alternatif olarak, geçişler olmadan, yalnızca bir SQL komutunu kullanarak çalıştırabilirsiniz.

context.Database.ExecuteSqlCommand(sql);

burada contexttüretilmiş bağlam sınıfınızın bir örneğidir ve sqldize olarak yukarıdaki SQL komutudur.

Tüm bu EF ile ParentIdbir ilişkiyi tanımlayan yabancı anahtarın hiçbir ipucu olmadığını unutmayın . EF bunu yalnızca sıradan bir skaler özellik olarak değerlendirecektir. Her nasılsa yukarıdakilerin tümü, bir SQL yönetim aracını açmaya ve kısıtlamayı elle eklemeye kıyasla yalnızca daha karmaşık ve daha yavaş bir yoldur.


2
Basitleştirme otomasyondan geliyor: Kodumun dağıtıldığı diğer ortamlara erişimim yok. Kodda bu değişiklikleri yapabilmek benim için güzel. But I like the snark :)
pomeroy

Ayrıca varlıkta bir öznitelik belirlediğinizi düşünüyorum, bu nedenle ebeveyn kimliğinde sadece ekleyin [ForeignKey("ParentTableName")]. Bu, özelliği, üst tablodaki anahtarın ne olduğu ile ilişkilendirir. Şimdi yine de sabit kodlanmış bir tablo adınız var.
Triynko

2
Kesinlikle imkansız değil, aşağıdaki diğer yoruma bakın Bu neden doğru cevap olarak işaretlendi
Igor Be

112

Bu yazı için olsa Entity Frameworkdeğil Entity Framework Core, (Ben v1.1.2 kullanıyorum) Varlık Framework Çekirdek kullanarak aynı şeyi elde etmek isteyen biri için yararlı olabilir.

Gezinme özelliklerine ihtiyacım yok (güzel olsalar da) çünkü DDD alıştırması yapıyorum Parentve Childiki ayrı toplu kök olmak istiyorum . Birbirleriyle altyapıya özel Entity Frameworknavigasyon özellikleri üzerinden değil yabancı anahtar aracılığıyla konuşabilmelerini istiyorum .

Tek yapmanız gereken , gezinme özelliklerini kullanarak HasOneve WithManybelirtmeden (sonuçta orada değiller) ilişkiyi bir tarafta yapılandırmaktır .

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");
        });

        ......
    }
}

Varlık özelliklerinin nasıl yapılandırılacağına dair örnekler de veriyorum, ancak burada en önemlisi HasOne<>, WithMany()ve HasForeignKey().

Umarım yardımcı olur.


9
Bu EF Core için doğru yanıttır ve DDD uygulayanlar için bu bir ZORUNLULUKTUR.
Thiago Silva

1
Seyrüsefer özelliğini kaldırmak için neyin değiştiğinden emin değilim. Lütfen açıklar mısın?
andrew.rockwell

3
@ andrew.rockwell: Bkz HasOne<Parent>()ve .WithMany()thie çocuk yapılandırmasına. Zaten tanımlanmış gezinme özellikleri olmadığından gezinme özelliklerine hiç başvurmazlar. Güncellemelerimle daha net hale getirmeye çalışacağım.
David Liang

Harika. Teşekkürler @DavidLiang
andrew.rockwell

2
@ mirind4 OP'deki belirli kod örneğiyle ilgisiz, DDD'ye göre farklı toplu kök varlıkları ilgili DB tablolarıyla eşliyorsanız, bu kök varlıklar yalnızca kendi kimlikleri aracılığıyla birbirlerine başvurmalı ve tam bir referans içermemelidir diğer AR varlığına. Aslında, DDD'de int / log / guid (özellikle AR varlıkları) gibi ilkel türlere sahip props kullanmak yerine varlıkların kimliğini bir değer nesnesi yapmak, ilkel takıntıdan kaçınmak ve farklı AR'lerin değer aracılığıyla varlıkları referans almasına izin vermek de yaygındır. nesne kimliği türü. HTH
Thiago Silva

21

DataAnotations kullanmak isteyen ve Gezinme Özelliğini açığa çıkarmak istemeyenler için küçük bir ipucu - kullanın 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; }
}

İşte bu - cascade:trueafter ile yabancı anahtar Add-Migrationoluşturulacak.


1
Özel olarak bile olabilir
Marc Wittke

2
Çocuk yaratırken atamanız mı Parentyoksa sadece atamanız mı gerekir ParentId?
George Mauer

5
@MarcWittke virtualözellikleri olamaz private.
LINQ

@GeorgeMauer: Bu iyi bir soru! Teknik olarak her ikisi de işe yarar, ancak bunun gibi tutarsız kodlarınız olduğunda da sorun olur, çünkü geliştiriciler (özellikle yeni gelenler) neyi aktaracaklarından emin değiller.
David Liang

14

EF Core durumunda, mutlaka bir gezinti özelliği sağlamanız gerekmez. İlişkinin bir tarafında bir Yabancı Anahtar sağlayabilirsiniz. Fluent API ile basit bir örnek:

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; }
    }
}

2

.Net Core 3.1, EntityFramework 3.1.3 kullanıyorum. Etrafta arama yapıyordum ve bulduğum Çözüm, genel sürümünü kullanıyordu HasForeginKey<DependantEntityType>(e => e.ForeginKeyProperty). aşağıdaki gibi bire bir ilişki oluşturabilirsiniz:

builder.entity<Parent>()
.HasOne<Child>()
.WithOne<>()
.HasForeginKey<Child>(c => c.ParentId);

builder.entity<Child>()
    .Property(c => c.ParentId).IsRequired();

Umarım bu yardımcı olur veya en azından HasForeginKeyyöntemin nasıl kullanılacağına dair başka fikirler sağlar .


0

Benim nedenini değil navigasyon özelliklerini kullanarak sınıf bağımlılıkları olduğunu. Modellerimi farklı projelerde herhangi bir kombinasyonda kullanılabilen veya kullanılmayan birkaç montaja ayırdım. Dolayısıyla, başka bir derlemeden sınıfa nagivation özelliği olan varlığım varsa, kaçınmak istediğim derlemeye başvurmam gerekir (veya bu tam veri modelinin bir bölümünü kullanan herhangi bir proje onunla her şeyi taşıyacaktır).

Ayrıca, geçişler (otomatik geçişler kullanıyorum) ve ilk veritabanı oluşturma için kullanılan ayrı bir taşıma uygulamam var. Bu proje her şeye açık nedenlerle atıfta bulunuyor.

Çözüm C tarzıdır:

  • "kopyala" dosyayı hedef sınıf ile bağlantı yoluyla taşıma projesine (VS'de altanahtarla sürükle-bırak )
  • nagivation özelliğini (ve FK özelliğini) devre dışı bırak #if _MIGRATION
  • bu ön işlemci tanımını geçiş uygulamasında ayarlayın ve model projesinde ayarlamayın, böylece hiçbir şeye başvurmaz ( Contactörnekte sınıfla derlemeye başvurmayın).

Örneklem:

    public int? ContactId { get; set; }

#if _MIGRATION
    [ForeignKey(nameof(ContactId))]
    public Contact Contact { get; set; }
#endif

Elbette aynı şekilde usingyönergeyi devre dışı bırakmalı ve ad alanını değiştirmelisiniz.

Bundan sonra, tüm tüketiciler bu özelliği normal DB alanı olarak kullanabilir (ve gerekmiyorsa ek derlemelere başvurmayın), ancak DB sunucusu bunun FK olduğunu bilecek ve basamaklandırmayı kullanabilecektir. Çok kirli çözüm. Ama çalışıyor.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.