Как у меня есть свойства класса (с навигационными реквизитами) в качестве свойств объекта? Сложные типы не будут

В принципе у меня есть объект вроде:

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 будет работать. Любая помощь приветствуется.

Ответ 1

ComplexType ДОЛЖЕН БЫТЬ решением, но, к сожалению:

Сложный тип не может содержать свойства навигации. Источник

Список обходных решений:

Временное решение с разбиением таблиц

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 (для многоразового класса адресов)

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

Ответ 2

В дополнение к разбиению таблиц также есть еще 2 решения (не решения).

Наследование

Создать класс класса и наследовать его в каждом классе, который должен иметь адрес.
Свойства адреса смешаны с другими свойствами (так что, по-моему, я бы не применял это решение в вашем случае).

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 с несколькими типами. Проблема здесь в том, что у вас есть 2 таблицы

Ответ 3

Почти всякая сложная навигация возможна с быстрым api.

Google: Свободный Api и EntityTypeConfiguration