繁体   English   中英

实体框架Fluent API关系映射HasRequired()。WithRequired()在没有Map()的情况下无法正确运行

[英]Entity Framework Fluent API relationship mapping HasRequired().WithRequired() not behaving correctly without Map()

博客模型

using System.Collections.Generic;

namespace DataLayer
{
    public class Blog
    {
        public int BlogKey { get; set; }
        public string Title { get; set; }
        public string BloggerName { get; set; }
        public virtual Post Post { get; set; }
    }
}

邮政模型

using System;
using System.Collections.Generic;

namespace DataLayer
{
    public class Post
    {
        public int PostKey { get; set; }
        public string Title { get; set; }
        public DateTime? DateCreated { get; set; }
        public string Content { get; set; }
        public virtual Blog Blog { get; set; }
    }
}

模型配置

using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration;

namespace DataLayer
{
    public class BlogConfiguration : EntityTypeConfiguration<Blog>
    {
        public BlogConfiguration()
        {
            ToTable("Blog", "dbo");
            HasKey(k => k.BlogKey).Property(p=>p.BlogKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

// This will allow having null Post for the Blog
            //HasRequired(p => p.Post).WithRequiredPrincipal(p => p.Blog).WillCascadeOnDelete(false);

// This will NOT allow having no Post for the Blog
            HasRequired(p => p.Post).WithRequiredPrincipal(p => p.Blog).Map(m=>m.MapKey("OtherBlogKeyColumn")).WillCascadeOnDelete(false);
        }
    }

    public class PostConfiguration : EntityTypeConfiguration<Post>
    {
        public PostConfiguration()
        {
            ToTable("Post", "dbo");
            HasKey(k => k.PostKey).Property(p=>p.PostKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

        }
    }
}

客户

using DataLayer;
using System;

namespace Client
{
    class Program
    {
        static void Main(string[] args)
        {
            MyDbContext c = new MyDbContext();

            //Works when dependent's foreign key column is mapped to the primary key column(this is by default when Map() is not provided). 
            //Doesn't work when foreign key column is mapped to some other column(which is provided by Map())
            Blog blog = new Blog { Title = "world", Post = null, BloggerName = "suyash" };


            //Blog required, Post required
            //Blog blog = new Blog { Title = "work", Post = new Post { Title = "new world post" }, BloggerName = "suyash" };

            c.Blogs.Add(blog);

            c.SaveChanges();

        }
    }
}

我有模型BlogPost 这里讨论的关系是HasRequired()。WithRequired()。 我希望Blog成为负责人,Post成为依赖人 请参阅博客配置。

HasRequired(p => p.Post).WithRequiredPrincipal(p => p.Blog).WillCascadeOnDelete(false); 允许 Blog blog = new Blog { Title = "world", Post = null, BloggerName = "suyash" }; 为空的 Blog blog = new Blog { Title = "world", Post = null, BloggerName = "suyash" };

但是, HasRequired(p => p.Post).WithRequiredPrincipal(p => p.Blog).Map(m=>m.MapKey("OtherBlogKeyColumn")).WillCascadeOnDelete(false); 没有。

Map()的配置按预期工作,当我们尝试插入空Post时,它将引发错误。

这不是HasRequired()。WithRequired()的全部目的,即使没有使用Map(),也要确保两端都有一个值。 当前没有Map(), 它的工作方式与HasOptional(Blog).WithRequired(Post)相同。

我想了解这是一个真正的错误,还是我在这里错过了一些东西。

必需≠必需

HasRequired - WithRequired组合比实际承诺的更多。 它使您可以存储唯一的原则,而无需附带依赖项。

在关系数据库中(至少在我所知道的实现中),没有办法“同时”将两行存储在不同的表中(即作为原子操作)。 因此,没有办法强制执行相互要求的1:1关联。 必须先插入主体实体,然后是从属实体。

但这是否应该阻止EF要求建立关联? 我认为像其他验证一样,在保存更改之前可以验证需求。 我认为他们可以强制执行HasRequired - WithRequired是真正需要的。 但是他们没有。

如果我们有这两个简单的类...

public class Principal
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual Dependent Dependent { get; set; }
}

public class Dependent
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual Principal Principal { get; set; }
}

...映射为...

modelBuilder.Entity<Principal>()
            .HasRequired(pr => pr.Dependent)
            .WithRequiredPrincipal(dep => dep.Principal);

...我们可以做的...

var principle1 = new Principal { Name = "Principal1" };
context.Principals.Add(principle1);
context.SaveChanges();

...而EF很高兴。

没有Principle我们不能插入Dependent 这是因为Dependent的主键是从Principle的复制而来的,并且同时是外键。 但这是1:0..1,而不是1:1。 如果我们将关联映射为...

modelBuilder.Entity<Principal>()
            .HasOptional(pr => pr.Dependent)
            .WithRequired(dep => dep.Principal);

...我们得到相同的数据模型。

备择方案?

获得真正需要的 1:1关联的一种方法是实体拆分 一个常见的例子是:

modelBuilder.Entity<Department>() 
    .Map(m => 
    { 
        m.Properties(t => new { t.DepartmentID, t.Name }); 
        m.ToTable("Department"); 
    }) 
    .Map(m => 
    { 
        m.Properties(t => new { t.DepartmentID, t.Administrator, t.StartDate, t.Budget }); 
        m.ToTable("DepartmentDetails"); 
    });

这还将创建一个具有“从属”(此处为DepartmentDetails )的数据模型,该数据模型共享“原理”的PK并通过FK进行引用。 现在,如果没有Department就无法创建DepartmentDetails因为我们只能创建Department对象, DepartmentDetails不是类,并且数据库中始终有两个记录。

但是,这当然与在类模型中具有两个实体完全不同。 两种型号的用法完全不同。 例如,当您查询DepartmentDepartmentDetails始终被加入。因此,这是真正的1:1(在数据模型中),但不能替代1:1作为类。

另一种方法是映射复杂类型 一个非常基本的示例是:

public class Person
{
    public int Id { get; set; }
    public Address Address { get; set; }
}

public class Address
{
    public string Street { get; set; }
    public int Number { get; set; }
    public string Town { get; set; }
}

没有任何映射,这将创建一个包含“ Person和“ Address中所有字段的表(EF推断“ Address为复杂类型,因为未定义键)。 现在,我们有两个类,并且如果没有地址,我们将无法创建Person (并且反向)。 但是现在我们无法从数据库中获取地址。 他们有点太依赖了。 Address不是实体,因为它没有身份。 这是一个值类型 同样,这是另一种替代方法,但用途完全不同。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM