Я сомневаюсь, что сопоставление, которое вы пробуете, возможно. Я бы попробовал что-то похожее на это:
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>();