如何配置与EntityFramework的一对一可选关系?

时间:2017-02-12 11:11:01

标签: c# .net entity-framework ef-code-first ef-migrations

这是我所拥有的最小测试案例。

public class Project
{
  public int ProjectId { get; set; }
  public string Name { get; set; }
}
public class Claim
{
  [Key]
  public int ClaimId { get; set; }
  public int ProjectId { get; set; }
  public virtual Project Project { get; set; }
  public virtual CommentDiscussion Discussion { get; set; }
}

public class ForumThread
{
  [Key]
  public int ForumThreadId { get; set; }
  public int ProjectId { get; set; }
  public virtual Project Project { get; set; }
  public virtual CommentDiscussion Discussion { get; set; }
}

public class CommentDiscussion
{
  public int CommentDiscussionId { get; set; }
  public int? ClaimId { get; set; }
  public virtual Claim Claim { get; set; }
  public int? ForumThreadId { get; set; }
  public virtual ForumThread ForumThread { get; set; }
  public int ProjectId { get; set; }
  public virtual Project Project { get; set; }
}
  modelBuilder.Entity<Claim>().HasRequired(c => c.CommentDiscussion).WithRequiredDependent(cd => cd.Claim);
  modelBuilder.Entity<ForumThread>().HasRequired(c => c.CommentDiscussion).WithRequiredDependent(cd => cd.ForumThread);

我希望配置以下关系:Claim只有一个CommentDiscussion&amp; ForumThread只有一个CommentDiscussionCommentDiscussion可以有ClaimForumThread

如果创建一个迁移,一切都很好。但是,如果我拆分为两个迁移,并首先创建除CommentDiscussion之外的所有内容,则添加CommentDiscussion的迁移将生成:

  AddForeignKey("dbo.ForumThreads", "ForumThreadId", "dbo.CommentDiscussions", "CommentDiscussionId");
  AddForeignKey("dbo.Claims", "ClaimId", "dbo.CommentDiscussions", "CommentDiscussionId");

这是非常错误的。即使我手动修复迁移,EF也会在加载时错误地映射所有内容。

1 个答案:

答案 0 :(得分:-1)

你必须要照顾两件事。你想要存储什么以及什么是有效的。并非所有可能的值都有效。

在这种情况下,CommentDiscussion必须包含ClaimForumThread引用,但不得同时设置。

实体:

public class Claim
{
    public int Id { get; set; }
    public string Name { get; set; }
    public CommentDiscussion CommentDiscussion { get; set; }
}

public class ForumThread
{
    public int Id { get; set; }
    public string Name { get; set; }
    public CommentDiscussion CommentDiscussion { get; set; }
}

public class CommentDiscussion
{
    public int Id { get; set; }
    public int? ClaimId { get; set; }
    public Claim Claim { get; set; }
    public int? ForumThreadId { get; set; }
    public ForumThread ForumThread { get; set; }
}

配置

public class ClaimConfiguration : EntityTypeConfiguration<Claim>
{
    public ClaimConfiguration()
    {
        HasKey( e => e.Id );
        Property( e => e.Name )
            .IsRequired( )
            .HasMaxLength( 100 );
    }
}

public class ForumThreadConfiguration : EntityTypeConfiguration<ForumThread>
{
    public ForumThreadConfiguration()
    {
        HasKey( e => e.Id );
        Property( e => e.Name )
            .IsRequired( )
            .HasMaxLength( 100 );
    }
}

public class CommentDiscussionConfiguration : EntityTypeConfiguration<CommentDiscussion>
{
    public CommentDiscussionConfiguration()
    {
        HasKey( e => e.Id );
        HasOptional( e => e.Claim )
            .WithRequired( m => m.CommentDiscussion )
            .Map( cfg => cfg.MapKey( nameof( CommentDiscussion.ClaimId ) ) );
        HasOptional( e => e.ForumThread )
            .WithRequired( m => m.CommentDiscussion )
            .Map( cfg => cfg.MapKey( nameof( CommentDiscussion.ForumThreadId ) ) );
    }
}

确保CommentDiscussion的验证只能引用ClaimForumThread

public class ModelContext : DbContext
{
    // Der Kontext wurde für die Verwendung einer ModelContext-Verbindungszeichenfolge aus der
    // Konfigurationsdatei ('App.config' oder 'Web.config') der Anwendung konfiguriert. Diese Verbindungszeichenfolge hat standardmäßig die
    // Datenbank 'ConsoleApp7.Model.ModelContext' auf der LocalDb-Instanz als Ziel.
    //
    // Wenn Sie eine andere Datenbank und/oder einen anderen Anbieter als Ziel verwenden möchten, ändern Sie die ModelContext-Zeichenfolge
    // in der Anwendungskonfigurationsdatei.
    public ModelContext()
        : base( "name=ModelContext" )
    {
    }

    protected override void OnModelCreating( DbModelBuilder modelBuilder )
    {
        modelBuilder.Configurations.Add( new ClaimConfiguration( ) );
        modelBuilder.Configurations.Add( new ForumThreadConfiguration( ) );
        modelBuilder.Configurations.Add( new CommentDiscussionConfiguration( ) );

        base.OnModelCreating( modelBuilder );
    }

    protected override bool ShouldValidateEntity( DbEntityEntry entityEntry )
    {
        return base.ShouldValidateEntity( entityEntry );
    }

    protected override DbEntityValidationResult ValidateEntity( DbEntityEntry entityEntry, IDictionary<object, object> items )
    {
        if ( entityEntry.Entity is CommentDiscussion )
        {
            if ( !entityEntry.CurrentValues.GetValue<int?>( nameof( CommentDiscussion.ClaimId ) ).HasValue && !entityEntry.CurrentValues.GetValue<int?>( nameof( CommentDiscussion.ForumThreadId ) ).HasValue )
            {
                var list = new List<System.Data.Entity.Validation.DbValidationError>( );
                list.Add( new System.Data.Entity.Validation.DbValidationError( nameof( CommentDiscussion.Claim ), "Claim or ForumThread is required" ) );
                list.Add( new System.Data.Entity.Validation.DbValidationError( nameof( CommentDiscussion.ForumThread ), "Claim or ForumThread is required" ) );

                return new System.Data.Entity.Validation.DbEntityValidationResult( entityEntry, list );
            }

            if ( entityEntry.CurrentValues.GetValue<int?>( nameof( CommentDiscussion.ClaimId ) ).HasValue && entityEntry.CurrentValues.GetValue<int?>( nameof( CommentDiscussion.ForumThreadId ) ).HasValue )
            {
                var list = new List<System.Data.Entity.Validation.DbValidationError>( );
                list.Add( new System.Data.Entity.Validation.DbValidationError( nameof( CommentDiscussion.Claim ), "Only Claim or ForumThread is possible, not both" ) );
                list.Add( new System.Data.Entity.Validation.DbValidationError( nameof( CommentDiscussion.ForumThread ), "Only Claim or ForumThread is possible, not both" ) );

                return new System.Data.Entity.Validation.DbEntityValidationResult( entityEntry, list );
            }
        }
        return base.ValidateEntity( entityEntry, items );
    }
}

检查

using ( var context = new ModelContext() )
{

    CommentDiscussion discussion = new CommentDiscussion( );
    context.Set<CommentDiscussion>( ).Add( discussion );
    try
    {
        context.SaveChanges( );
    }
    catch ( Exception ex )
    {
        Console.WriteLine( ex.ToString() );
    }

    Claim claim = new Claim( );
    ForumThread forumThread = new ForumThread( );

    claim.CommentDiscussion = discussion;
    forumThread.CommentDiscussion = discussion;

    try
    {
        context.SaveChanges( );
    }
    catch ( Exception ex )
    {
        Console.WriteLine( ex.ToString( ) );
    }
}

<强>更新

这里为模型生成了迁移

public partial class InitialCreate : DbMigration
{
    public override void Up()
    {
        CreateTable(
            "dbo.Claims",
            c => new
                {
                    Id = c.Int(nullable: false, identity: true),
                    Name = c.String(nullable: false, maxLength: 100),
                    ClaimId = c.Int(nullable: false),
                })
            .PrimaryKey(t => t.Id)
            .ForeignKey("dbo.CommentDiscussions", t => t.ClaimId)
            .Index(t => t.ClaimId);

        CreateTable(
            "dbo.CommentDiscussions",
            c => new
                {
                    Id = c.Int(nullable: false, identity: true),
                    ClaimId = c.Int(),
                    ForumThreadId = c.Int(),
                })
            .PrimaryKey(t => t.Id);

        CreateTable(
            "dbo.ForumThreads",
            c => new
                {
                    Id = c.Int(nullable: false, identity: true),
                    Name = c.String(nullable: false, maxLength: 100),
                    ForumThreadId = c.Int(nullable: false),
                })
            .PrimaryKey(t => t.Id)
            .ForeignKey("dbo.CommentDiscussions", t => t.ForumThreadId)
            .Index(t => t.ForumThreadId);

    }

    public override void Down()
    {
        DropForeignKey("dbo.ForumThreads", "ForumThreadId", "dbo.CommentDiscussions");
        DropForeignKey("dbo.Claims", "ClaimId", "dbo.CommentDiscussions");
        DropIndex("dbo.ForumThreads", new[] { "ForumThreadId" });
        DropIndex("dbo.Claims", new[] { "ClaimId" });
        DropTable("dbo.ForumThreads");
        DropTable("dbo.CommentDiscussions");
        DropTable("dbo.Claims");
    }
}