n: m с полезной нагрузкой в ​​Entity Framework 4.x - PullRequest
2 голосов
/ 21 июля 2011

Я пытаюсь создать следующее отношение в EF4.x

«один материал состоит из множества материалов на определенное количество, и каждый материал может использоваться в материалах»

в идеале это будет преобразовано в Материал n: m Материал, но Контент является полезной нагрузкой, поэтому я перевел это на:

«Материал 1: n MaterialUsage» и «МатериалUsage m: 1 Материал»

Я создал две таблицы, поскольку у меня есть полезная нагрузка (определенное количество)

'Material' и 'MaterialUsage'

в материале я определил отношение 'IsMadeOf', которое ссылается на 'MaterialUsage.IsUsedIn' и отношение IsUsedFor, которое ссылается на MaterialUsage.IsMadeOf

в MaterialUsage У меня есть два вышеописанных поля «Содержимое».

Теперь к моей проблеме:

Если я удаляю Материал, я сталкиваюсь с сообщением об ошибке, по сути говоря, что внутри ассоциации «MaterialMaterialUsage», которая идентифицирует отношение «Material.IsUsedFor <-> MaterialUsage.IsMadeOf», отношение находится в статусе «Удалено» и должно быть согласно определению кратности соответствующая запись «MaterialUsage» также должна иметь статус «Удалено», который не был найден.

Однако я намерен удалить материал и все «MaterialUsages», которые идентифицированы через «Material.IsMadeOf». Который не включает материалы, на которые ссылаются, а только записи «MaterialUsage», имеющие ссылку в «IsUsedIN» на материал, который необходимо удалить.

Теперь я пытаюсь найти четкий способ сделать это. Я предполагаю, что это могло бы работать с ссылочной целостностью, но я не слишком знаком с этим, и поэтому я потерян.

Я могу изменить DB-Design без проблем.

1 Ответ

1 голос
/ 21 июля 2011

Я попробую недоделанную смесь ответа и вопроса.В 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...
}
...