如何将类属性(带有导航属性)作为实体属性?复杂类型不可行。

10

基本上我有一个实体,如下:

public class Person {
 public int PersonId { get; set; }
 public string Name { get; set; }
 public Address Hometown { get; set; }
}

并且一个类的形式如下:

public class Address {
 public City City { get; set; }
 public string Province { get; set; }
}

我想实现的目标是在竖直方向上连接两个类,并建立一张带有行的表格:

TB_PERSON:
   PersonId PK
   Name
   City_id FK
   Province

我希望采用这种方法的原因是,在我的真实项目中,我有多个条目中出现了同一种数据结构模式,例如地址类。在这种情况下,它很容易出现在另一个实体中。

这件事难道就那么难吗?我已经找了好几天了,最接近的可能是复杂类型,但是在这种情况下,它们不允许导航属性。我想访问并且让我的行数据更具有结构化和面向对象的特点,所以我觉得EF会做到。感谢任何帮助。


public Auth Auth Your variable name and type cannot be identical.You could access CardType and WhateverField by doing myPerson.auth.cardType and myPerson.auth.whateverField - Xander Luciano
感谢您的回答,但是很抱歉那并不是我所询问的。 - Emirhan Özlen
3个回答

3

复杂类型本应该是一种解决方案,但不幸的是:

复杂类型不能包含导航属性。 来源

解决方法列表:

采用表分割的解决方法

public class Person
{
    public int PersonID { get; set; }
    public string Name { get; set; }
    public virtual Address Address { get; set; }
}

public class Address
{
    public Int32 ID { get; set; }
    public string Province { get; set; }
    public virtual City City { get; set; }

}

public class City
{
    public Int32 CityID { get; set; }
    public string Name { get; set; }
}

public class MappingContext : DbContext
{
    public DbSet<Person> Persons { get; set; }

    public DbSet<Address> Addresses { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Address>()
            .HasKey(t => t.ID)
            .HasOptional(t => t.City)
            .WithMany()
            .Map(t => t.MapKey("CityID"));

        modelBuilder.Entity<Address>()
            .Property(t => t.ID)
            .HasColumnName("PersonID");

        modelBuilder.Entity<Person>()
            .HasKey(t => t.PersonID)
            .HasRequired(t => t.Address)
            .WithRequiredPrincipal();

        modelBuilder.Entity<Person>().ToTable("TB_PERSON");

        modelBuilder.Entity<Address>().ToTable("TB_PERSON");

        modelBuilder.Entity<City>()
            .HasKey(t => t.CityID)
            .ToTable("City");
    }
}

[用法]

    using (var db = new MappingContext())
    {
        var person = db.Persons.FirstOrDefault();
        var cityName = person.Address.City.Name;

        var address = db.Addresses.FirstOrDefault();
        var personName = address.Person.Name;
    }

[数据库]
    CREATE TABLE [dbo].[City](
        [CityID] [int] IDENTITY(1,1) NOT NULL,
        [Name] [varchar](50) NULL
    ) ON [PRIMARY]

    CREATE TABLE [dbo].[TB_PERSON](
        [PersonId] [int] IDENTITY(1,1) NOT NULL,
        [Name] [varchar](50) NULL,
        [Province] [varchar](50) NULL,
        [CityID] [int] NULL
    ) ON [PRIMARY]

使用表分割+TPC继承(为可重用的Address类)来解决问题

TB_CUSTOMER是另一个带有地址列的表。

public class Person
{
    public int PersonID { get; set; }
    public string Name { get; set; }
    public virtual PersonAddress Address { get; set; }
}

public class Address
{
    public string Province { get; set; }
    public virtual City City { get; set; }

}

public class PersonAddress : Address
{
    public Int32 PersonID { get; set; }
    public virtual Person Person { get; set; }
}
public class CustomerAddress : Address
{
    public Int32 CustomerID { get; set; }
}

public class Customer
{
    public int CustomerID { get; set; }
    public string Name { get; set; }
    public virtual CustomerAddress Address { get; set; }
}

public class City
{
    public Int32 CityID { get; set; }
    public string Name { get; set; }
}

public class MappingContext : DbContext
{
    public DbSet<Person> Persons { get; set; }
    public DbSet<Customer> Customers { get; set; }
    public DbSet<PersonAddress> PersonAddresses { get; set; }
    public DbSet<CustomerAddress> CustomerAddresses { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<PersonAddress>()
            .HasKey(t => t.PersonID)
            .HasOptional(t => t.City)
            .WithMany()
            .Map(t => t.MapKey("CityID"));

        modelBuilder.Entity<CustomerAddress>()
            .HasKey(t => t.CustomerID)
            .HasOptional(t => t.City)
            .WithMany()
            .Map(t => t.MapKey("CityID"));

        modelBuilder.Entity<Person>()
            .HasRequired(t => t.Address)
            .WithRequiredPrincipal(t => t.Person);

        modelBuilder.Entity<Customer>()
            .HasRequired(t => t.Address)
            .WithRequiredPrincipal();

        modelBuilder.Entity<Person>().ToTable("TB_PERSON");
        modelBuilder.Entity<PersonAddress>().ToTable("TB_PERSON");

        modelBuilder.Entity<Customer>().ToTable("TB_CUSTOMER");
        modelBuilder.Entity<CustomerAddress>().ToTable("TB_CUSTOMER");

        modelBuilder.Entity<City>()
            .HasKey(t => t.CityID)
            .ToTable("City");
    }
}

使用IAddress进行解决

public class Person : IAddress
{
    public int PersonID { get; set; }
    public string Name { get; set; }
    public string Province { get; set; }
    public virtual City City { get; set; }

    [NotMapped]
    public IAddress Address { get { return this; } }
}

public interface IAddress
{
    string Province { get; set; }
    City City { get; set; }

}

public class City
{
    public Int32 CityID { get; set; }
    public string Name { get; set; }
}

public class MappingContext : DbContext
{
    public DbSet<Person> Persons { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {

        modelBuilder.Entity<Person>()
            .HasKey(t => t.PersonID)
            .HasOptional(t => t.City)
            .WithMany()
            .Map(t => t.MapKey("CityID"));

        modelBuilder.Entity<Person>().ToTable("TB_PERSON");

        modelBuilder.Entity<City>()
            .HasKey(t => t.CityID)
            .ToTable("City");
    }
}

感谢您的答案,我不知道表格拆分。但是这个类出现了多个条目,所以如果我选择进行表格拆分,则必须为每个其他实体执行该操作。 - Emirhan Özlen
+1 for "表拆分"。使用此模型,还需从地址到人员的导航属性中删除(使用复杂类型时没有此功能),您需要为每个使用它的实体创建一个地址类(例如,在此示例中为Person创建一个,为Customer创建一个等等),我是对的吗? - bubi
你是对的,Address类不必有导航属性到Person(为了对Person进行此配置,应该以.WithRequiredPrincipal()结尾)。 要在其他映射中重用Address类(例如Customer),我认为我们需要将表拆分与Address的某些继承策略(例如TPC)相结合。 可重用的Address类将作为基类。PersonAddress和CustomerAddress将具有额外的Person和Customer属性,并使用表拆分策略进行映射。 - Daprpaz
感谢您提供如此详细的答案。我认为这种表拆分会导致PersonId出现两次,而如果我只能使用一个带有字段的单个表:(id,addr,prov等...),那么可以节省一些数据库大小。但我想目前aspnet没有提供这种方式。自从我提出这个问题以来,我想到的解决方案是创建一个接口来强制实体应用IAddress和字段。然后我可以像(entity as IAddress).provinceId这样进行转换,并仍然可以访问地址属性。如果您愿意,您可以在我的解决方案上工作,我会给您奖励。 - Emirhan Özlen
在每个带有地址列的实体中使用IAddress接口将会混合地址属性和该实体的属性(就像bubi在他的继承解决方案中所写的那样)。我将其添加为另一个示例。Address类中的PersonId可以有其他名称(我将在上面的示例中进行更改以便更好地理解),但它仍然是映射到TB_PERSON中的一个列的一对一映射。 - Daprpaz
感谢提供如此广泛的解决方案来解决这个问题。我希望在未来,这将为那些试图处理这个问题的人们提供解决方案。此外,对于巧妙地使用非映射属性,公共IAddr addr { get { return this; } },点赞+1。 - Emirhan Özlen

1

除了表拆分之外,还有两种解决方法(而不是解决方案)。

继承

创建地址类并在应该有地址的每个类中继承它。
地址属性与其他属性混合在一起(所以实际上我认为在您的情况下不适用这种解决方案)。

1-1关系
(如果更多实体可以共享相同的地址则为n-1关系)

模型:

public class ClassA
{
    public int Id { get; set; }
    public string Description { get; set; }
    public virtual ClassB ClassB { get; set; }
}

public class ClassB
{
    public int Id { get; set; }
    public string Description { get; set; }
    public virtual ClassA ClassA { get; set; }
}

上下文:

class Context : DbContext
{
    public Context(DbConnection connection)
        : base(connection, false)
    { }

    public DbSet<ClassA> As { get; set; }
    public DbSet<ClassB> Bs { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<ClassB>().HasOptional(c => c.ClassA).WithOptionalDependent(c => c.ClassB);
    }
}

DDL语句:

ExecuteNonQuery==========
CREATE TABLE [ClassAs] (
 [Id] int not null identity(1,1)
, [Description] text null
);
ALTER TABLE [ClassAs] ADD CONSTRAINT [PK_ClassAs_9cd06620] PRIMARY KEY ([Id])
ExecuteNonQuery==========
CREATE TABLE [ClassBs] (
 [Id] int not null identity(1,1)
, [Description] text null
, [ClassA_Id] int null
);
ALTER TABLE [ClassBs] ADD CONSTRAINT [PK_ClassBs_9cd06620] PRIMARY KEY ([Id])
ExecuteNonQuery==========
CREATE INDEX [IX_ClassA_Id] ON [ClassBs] ([ClassA_Id])
ExecuteNonQuery==========
ALTER TABLE [ClassBs] ADD CONSTRAINT [FK_ClassBs_ClassAs_ClassA_Id] FOREIGN KEY ([ClassA_Id]) REFERENCES [ClassAs] ([Id])

在第二种情况下,您可以删除ClassB.ClassA导航属性,以便可以在多个类型之间共享ClassB。问题在于您有两个表格。

0

使用流畅的 API 几乎可以实现任何复杂的导航。

谷歌这些内容:流畅的 API 和 EntityTypeConfiguration。


谢谢,我会找这些资料的。同时,您可以改进您的回答并提供一个解决方案,这样我就可以将您的回复标记为被接受的答案。 - Emirhan Özlen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接