Как сопоставить различные свойства навигации в типах TPH с одной и той же таблицей? - PullRequest
2 голосов
/ 10 сентября 2011

У меня есть эта существующая схема базы данных, которая подразумевает самостоятельную ссылку "многие ко многим" с использованием объединенной таблицы. Таблица Местоположение может содержать информацию Страна , Город , Район , или Район в соответствии с полем Disciminator. Таблица RelatedLocation содержит отношения самоссылки. Schema

Моя модель домена выглядит следующим образом, где класс Location является абстрактным, и каждый унаследованный класс содержит связанные свойства навигации.

public abstract class Location
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Country : Location
{
    public virtual ICollection<District> Districts { get; set; }
}

public class District : Location
{
    public virtual ICollection<Country> Countries { get; set; }
    public virtual ICollection<City> Cities { get; set; }
}

public class City : Location
{
    public virtual ICollection<District> Districts { get; set; }
    public virtual ICollection<Area> Areas { get; set; }
}

public class Area : Location
{
    public virtual ICollection<City> Cities { get; set; }
}

В OnModelCreating Я использую следующее, чтобы отобразить каждое унаследованное отношение «многие ко многим» класса

modelBuilder.Entity<Country>()
            .HasMany(c => c.Districts)
            .WithMany(d => d.Countries)
            .Map(t => t.ToTable("RelatedLocations").MapLeftKey("ParentId").MapRightKey("RelatedId"));
modelBuilder.Entity<City>()
            .HasMany(c => c.Districts)
            .WithMany(d => d.Cities)
            .Map(t => t.ToTable("RelatedLocations").MapLeftKey("ParentId").MapRightKey("RelatedId"));

После создания модели, которую я получаю и освобождаю ее от «Каждый EntitySet должен ссылаться на уникальную схему и таблицу», то есть EF жалуется на то, что сопоставляет различные отношения одной и той же таблице « RelatedLocaions » более одного раза.

Я не знаю, что сопоставление таким способом не поддерживается в EF4.1, или я отображаю его неверно!

1 Ответ

4 голосов
/ 10 сентября 2011

Я сомневаюсь, что сопоставление, которое вы пробуете, возможно. Я бы попробовал что-то похожее на это:

public abstract class Location
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Location> ParentLocations { get; set; }
    public virtual ICollection<Location> RelatedLocations { get; set; }
}

public class Country : Location
{
    // readonly = not mapped
    public IEnumerable<District> Districts
    {
        get { return RelatedLocations.OfType<District>(); }
    }
}

public class District : Location
{
    public IEnumerable<Country> Countries
    {
        get { return ParentLocations.OfType<Country>(); }
    }

    public IEnumerable<City> Cities
    {
        get { return RelatedLocations.OfType<City>(); }
    }
}

// same approch for the other collections

А затем это отображение:

modelBuilder.Entity<Location>()
            .HasMany(l => l.ParentLocations)
            .WithMany(l => l.RelatedLocations)
            .Map(t => t.ToTable("RelatedLocations")
                       .MapLeftKey("ParentId")
                       .MapRightKey("RelatedId"));

Отображение «многие ко многим» всегда происходит между ParentLocations и RelatedLocations, но эти коллекции заполнены различными экземплярами производных классов в соответствии с конкретным типом, с которым вы работаете. Коллекции только для чтения - это только помощники, которые выполняют приведение типов в памяти (на основе лениво загруженных ParentLocations и RelatedLocations) Location сущностей.

Редактировать

Возможно, вместо использования .OfType<T>(), который фильтрует всех объектов типа T из исходной коллекции, предпочтительным является .Cast<T>(), который пытается привести всех объектов в источнике collection для ввода T и выдает исключение, если приведение невозможно. Это должно в основном привести к тому же результату, потому что ICollection<Location> в вашем базовом классе всегда должен заполняться только одним и тем же производным типом. Например: Country.RelatedLocations должен содержать только объекты типа District. Но, возможно, в данном случае исключение является хорошим, поскольку оно указывает на то, что что-то не так, вместо того, чтобы молча игнорировать сущности другого типа в коллекциях (что будет делать OfType).

Редактировать 2

Хочу подчеркнуть, что коллекции IEnumerable являются помощниками , которые позволяют вам извлекать сущности с производным типом. Коллекции просто выполняют приведение типов, не более того. Они не имеют ничего общего с отображением базы данных, EF даже не «видит», что они существуют. Вы можете удалить их, и ничего не изменится в модели EF и столбцах таблицы базы данных, отношениях и ссылочных ограничениях.

Как бы вы добавили и получили объекты в этой модели? Примеры:

  • Добавить новый Country со списком District объектов:

    var country = new Country() { RelatedLocations = new List<Location>() };
    country.Name = "Palau";
    // ParentLocations stays empty because Country has no parents
    var district1 = new District { Name = "District1" };
    var district2 = new District { Name = "District2" };
    country.RelatedLocations.Add(district1); // because District is a Location
    country.RelatedLocations.Add(district2);
    context.Locations.Add(country); // because Country is a Location
    context.SaveChanges();
    
  • Получить эту сущность снова:

    var country = context.Locations.OfType<Country>()
        .SingleOrDefault(c => c.Name == "Palau");
    // now get the districts, RelatedLocations is lazily loaded
    var districts = country.RelatedLocations.Cast<District>();
    // What type is districts? It's an IEnumerable<District>.
    // So we can also use a helper property:
    // var districts = country.Districts;
    
  • Получить район:

    var district = context.Locations.OfType<District>()
        .SingleOrDefault(d => d.Name == "District1");
    var countries = district.ParentLocations.Cast<Country>();
    // or with the helper: var countries = district.Countries;
    // countries collection contains Palau, because of many-to-many relation
    

Редактировать 3

Вместо создания Country с new вы можете создать ленивый загрузочный прокси. Тогда вам не нужно инициализировать коллекцию RelatedLocations. Мне было интересно, как это может работать с производным типом, но я только что обнаружил, что существует перегрузка Create с универсальным параметром для этой цели:

var country = context.Locations.Create<Country>();
...