Я попробую недоделанную смесь ответа и вопроса.В EF 4.1 с DbContext
API я бы создал следующие классы моделей (надеюсь, я правильно понял ваше описание):
public class Material
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<MaterialUsage> IsMadeOf { get; set; }
public ICollection<MaterialUsage> IsUsedFor { get; set; }
}
public class MaterialUsage
{
public int Id { get; set; }
public int Content { get; set; }
public Material IsUsedIn { get; set; }
public Material IsMadeOf { get; set; }
}
И этот производный контекст и отображение:
public class MyContext : DbContext
{
public DbSet<Material> Materials { get; set; }
public DbSet<MaterialUsage> MaterialUsages { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Material>()
.HasMany(m => m.IsMadeOf)
.WithRequired(m => m.IsUsedIn)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Material>()
.HasMany(m => m.IsUsedFor)
.WithRequired(m => m.IsMadeOf)
.WillCascadeOnDelete(false);
}
}
Я установил свойства навигации в MaterialUsage
на Required
, потому что я думаю, что MaterialUsage
не может существовать без ссылки на материалы.Это правильно?Насколько я вижу, необходимо отключить каскадное удаление, в противном случае EF будет жаловаться на множественные возможные пути каскадного удаления, которые не разрешены.
Теперь для создания материалов и их отношений что-то подобное будет работать:
using (var context = new MyContext())
{
var copper = new Material { Name = "Copper" };
context.Materials.Add(copper);
var zinc = new Material { Name = "Zinc" };
context.Materials.Add(zinc);
var brass = new Material
{
Name = "Brass",
IsMadeOf = new List<MaterialUsage>
{
new MaterialUsage { Content = 10, IsMadeOf = copper },
new MaterialUsage { Content = 20, IsMadeOf = zinc }
}
};
context.Materials.Add(brass);
context.SaveChanges();
}
Результат в базе данных:
Table Materials Table MaterialUsages
Id Name Id Content IsUsedIn_Id IsMadeOf_Id
--------- -------------------------------------------
1 Brass 1 10 1 2
2 Copper 2 20 1 3
3 Zinc
Теперь удаление затруднено, поскольку Material
появляется в обоих отношениях.Особенно я не знаю, как вы могли бы сделать это:
Однако я намерен удалить материал и все «MaterialUsages», которые идентифицированы через «Material.IsMadeOf»
Если я правильно понимаю, вы хотели бы сделать что-то вроде этого, чтобы удалить цинк:
var zinc = context.Materials
.Include(m => m.IsMadeOf)
.Where(m => m.Name == "Zinc")
.Single();
foreach (var usage in zinc.IsMadeOf.ToList())
context.MaterialUsages.Remove(usage);
context.Materials.Remove(zinc);
context.SaveChanges();
Это не работает, потому что цинк сделан из ничего (коллекция IsMadeOf
пуста, поэтому циклвыше ничего не делает).Но если вы удалите цинк сейчас, вы нарушите ограничение, а именно, что цинк используется для латуни.(Id = 2 в таблице MaterialUsages
не может существовать без цинка.)
По моему мнению, вы должны также удалить MaterialUsages
, которые обозначены Material.IsUsedFor
:
var zinc = context.Materials
.Include(m => m.IsMadeOf)
.Include(m => m.IsUsedFor)
.Where(m => m.Name == "Zinc")
.Single();
foreach (var usage in zinc.IsMadeOf.ToList())
context.MaterialUsages.Remove(usage);
foreach (var usage in zinc.IsUsedFor.ToList())
context.MaterialUsages.Remove(usage);
context.Materials.Remove(zinc);
context.SaveChanges();
Это приведет к удалению Id = 3 в таблице Materials
, а также Id = 2 в таблице MaterialsUsages
, полностью заполнив ссылочные ограничения.
Не уверен, что это именно то, что вам нужно.
Редактировать
Полагаю, теперь я вижу: вы на самом деле хотите, чтобы было исключение, которое выдается из-за нарушенного ограничения, когда выудалить цинк.Потому что: нельзя допускать удаление материала, если он используется в другом материале (цинк используется в латуни, поэтому запрещается удалять цинк, пока латунь находится в базе данных).Хорошо, тогда замена цинка на латунь в примере будет действительно работать:
var brass = context.Materials
.Include(m => m.IsMadeOf)
.Where(m => m.Name == "Brass")
.Single();
foreach (var usage in brass.IsMadeOf.ToList())
context.MaterialUsages.Remove(usage);
context.Materials.Remove(brass);
context.SaveChanges();
Он просто удаляет обе строки в таблице MaterialUsages
и латунь в таблице Material
.
Изменить 2
Если вы хотите проверить, используется ли удаляемый материал для какого-либо другого материала, вы можете проверить это, прежде чем пытаться действительно удалить:
if (context.Materials
.Where(m => m.Name == "Brass")
.Select(m => !m.IsUsedFor.Any())
.Single())
{
// the code snippet above
}
else
{
// "Brass" cannot be deleted since it is used for other materials...
}