Был недавно вопрос по возвратным отношения в Entity Framework, чтобы показать как можно подобное реализовать и была написана данная статья. Суть задачи следующая: есть сущность продукты (Products) и категории (Category), необходимо в описании каждого товара отображать
список "необходимых" и "дополнительных" продуктов. В языке C#, моделирование множественных возвратных связей на себя - не проблема. Проблемы возникают с физическим хранилищем, в данном случае это БД SQL Server. В реляционной базе нельзя смоделировать отношение многие ко многим без промежуточной таблицы, в C# можно. Поэтому нужно использовать отображение. Но нужна не одна, а две таблицы, одна на пару ключей, но сущностный класс будет один. Почему нужны две? Чтобы не иметь дублирование связей:
ProductID | DependProductID | AdditionallyProductID |
1 | 2 | 4 |
1 | 2 | 5 |
во второй строчке связь дублируется. Так как у одного продукта может быть только один необходимый (ProductDepend) и несколько дополнительных (ProductAdditionally) или наоборот. А если сделать две таблицы, то этого не будет. В конечном счёте всё будет выглядеть примерно так:
namespace CircularReference
{
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new Initializer());
MyDbContext db = new MyDbContext();
//Таблицы должны быть уже заполнена данными.
IQueryable<Product> products = db.Products.Select(p => p);
foreach(var p in products)
{
Console.WriteLine("Продукт: {0}\n Дополнительные: ", p.ProductId);
foreach (var addProd in p.DescendantAdditionallyProducts)
{
Console.WriteLine(" {0} ", addProd.ProductId);
}
}
}
}
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
//Не привожу список остальных свойств для краткости.
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
//Список товаров которые являются необходимыми для текущего.
public virtual List<Product> AncestorDependProducts { get; set; }
//Список товаров для которых теущий является необходимым.
public virtual List<Product> DescendantDependProducts { get; set; }
//Список товаров которые являются дополнительными для текущего.
public virtual List<Product> AncestorAdditionallyProducts { get; set; }
//Список товаров для которых теущий является дополнительным.
public virtual List<Product> DescendantAdditionallyProducts { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public virtual ICollection<Product> Products { get; set; }
//Не привожу список остальных свойств для краткости.
}
public class MyDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
//Используем FluentAPI для установки отношений.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Устанавливаем первичный ключ.
modelBuilder.Entity<Product>().HasKey(t => t.ProductId);
//Ставим множественные возвратные отношения на себя,
//через дополнительную таблицу с соответствующими ключами.
modelBuilder.Entity<Product>().HasMany(t => t.DescendantAdditionallyProducts)
.WithMany(t => t.AncestorAdditionallyProducts)
.Map(c => { c.ToTable("ProdAddProd");
c.MapLeftKey("ProductId"); c.MapRightKey("AdditionalProductId"); });
//Тоже самое тут.
modelBuilder.Entity<Product>().HasMany(t => t.DescendantDependProducts)
.WithMany(t => t.AncestorDependProducts)
.Map(c => { c.ToTable("ProdDepProd");
c.MapLeftKey("ProductId"); c.MapRightKey("DependProductId"); });
base.OnModelCreating(modelBuilder);
}
}
public class Initializer : IDatabaseInitializer<MyDbContext>
{
//Логика инициализации базы.
#region IDatabaseInitializer<MyDbContext> Members
public void InitializeDatabase(MyDbContext context)
{
bool dbExist = context.Database.Exists();
if (!dbExist)
{
context.Database.Create();
context.SaveChanges();
}
else
return;
}
#endregion
}
}
А схема БД будет такой:
Вот таким не очень сложным способом можно решить подобную задачу.