Правильный способ отображения отношений «один ко многим».При наличии одинакового отношения в нескольких объектах - PullRequest
0 голосов
/ 04 октября 2018

Предположим, следующая структура классов и отношений:

class Document
{
    public List<Version> DocumentVersions { get; set; }
    // Other properties
}

class Register
{
    public List<Version> RegisterVersions { get; set; }
    // Other properties
}

class Version
{
    public int VersionNumber { get; set; }
    // Other properties
}

При использовании EF Core, он собирается создать 3 таблицы, D, R и V соответственно, где V будет иметь 2 FK, однудля D и один для R.

Мои вопросы:

  • Является ли подход EF Core по умолчанию правильным?Не приведет ли это к недействительным состояниям, где V не имеет FK, потому что оба FK могут быть обнуляемыми.
  • Я прочитал this , и он почти ответил на мой первый вопрос, но приводит меня к другомувопрос:
    • Как я могу сказать EF следовать этому подходу: Должен ли я создавать производный тип V для каждого из его владельцев?или есть ли способ, которым я могу сопоставить одну сущность нескольким таблицам и сказать EF, какие отношения принадлежат какой таблице?

Возможно, стоит упомянуть, что мой пример упрощен, и в действительности у меня есть 6 сущностей, использующих одну и ту же сущность V.

Ответы [ 2 ]

0 голосов
/ 12 октября 2018

Итак, дилемма:

A) Должен ли я оставить два FK в Version или

B) построить две таблицы DocumentVersion и RegisterVersion вместо просто Version?

Ну, правда в том, что вы можете сделать и то, и другое.Вам просто нужно решить, какой подход лучше подходит вашей системе.Давайте кратко рассмотрим.

Подход A

Чтобы ответить на ваш вопрос;да, подход EF по умолчанию правильный.Создание двух FK и создание двух таблиц приведет к созданию двух FK.Это создаст дополнительную таблицу только в случае промежуточной таблицы для многих со многими участниками.

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

public class Version
{
    [Key]
    public int VersionNumber { get; set; }

    public int? DocumentID { get; set; }
    public virtual Document Document { get; set; }

    public int? RegisterID { get; set; }
    public virtual Register Register { get; set; }

    //Other properties
}

Поскольку Version имеет PK, он может создавать записибез какой-либо из ФК, имеющих какое-либо значение.Если это разрешено в вашей бизнес-модели, оставьте все как есть.Позже вы можете предоставить пользовательский интерфейс для назначения «Версий» либо «Документам», либо «Регистрам».

Если вы хотите добавить некоторые правила в вашу таблицу Version;например, каждая запись должна иметь хотя бы один FK или только один FK, это можно сделать, переопределив метод ValidateEntity вашего класса DbContext (или, возможно, с помощью некоторого ограничения sql в базе данных).

    protected override DbEntityValidationResult ValidateEntity(
        DbEntityEntry entityEntry, IDictionary<object, object> items)
    {
        // validate entity from annotations
        var result = base.ValidateEntity(entityEntry, items);

        // custom validation rules
        if (entityEntry.Entity is Version && 
            (entityEntry.State == EntityState.Added || entityEntry.State == EntityState.Modified))
        {
            Version version = ((Version)entityEntry.Entity);
            if (version.DocumentID == null && version.RegisterID == null)
                result.ValidationErrors.Add(new DbValidationError(null, "A document or register must be specified."));
        }

        return result;
    }

Обратите внимание, что вы можете создавать свои собственные аннотации для проверки свойств вашей сущности.Но они ограничены одной собственностью.Если вы хотите добавить проверки, которые объединяют более одного свойства, метод ValidateEntity - единственный известный мне способ.

Подход B

Есть два способа реализации этого подхода.Первый - сохранить таблицу Version и добавить две промежуточные таблицы сверху.

public class Document
{
    public virtual List<DocumentVersion>  Versions { get; set; }
    // other properties
}

public class Register
{
    public virtual List<RegisterVersion> Versions { get; set; }
    // other properties
}

public class Version
{
    [Key]
    public int VersionNumber { get; set; }

    //Other properties
}

public class DocumentVersion
{
    public int DocumentID { get; set; }
    public virtual Document Document { get; set; }

    public int VersionID { get; set; }
    public virtual Version Version { get; set; }

    // other properties
}

public class RegisterVersion
{
    public int RegisterID { get; set; }
    public virtual Register Register { get; set; }

    public int VersionID { get; set; }
    public virtual Version Version { get; set; }

    // other properties
}

На самом деле это разрешает отношения многие ко многим, но вы можете использовать это как один ко многим.Второй способ - сделать Version абстрактным (не таблица базы данных) и построить две новые таблицы для наследования от Version:

public class Document
{
    public virtual List<DocumentVersion>  Versions { get; set; }
    // other properties
}

public class Register
{
    public virtual List<RegisterVersion> Versions { get; set; }
    // other properties
}

// don't make this a DbSet
public abstract class Version
{
    [Key]
    public int VersionNumber { get; set; }

    //Other properties
}

public class DocumentVersion : Version
{
    public int DocumentID { get; set; }
    public virtual Document Document { get; set; }

    // other properties
}

public class RegisterVersion : Version
{
    public int RegisterID { get; set; }
    public virtual Register Register { get; set; }}

    // other properties
}

Это правильное и понятное отношение один ко многим.

Заключение Суть в том, что вы можете использовать любой из двух подходов и с изменениями, которые соответствуют вашим потребностям.

Я успешно использовал оба подхода, но я предпочитаю второй (и с наследованием абстрактного класса).Первый подход кажется более эффективным способом сокращения ресурсов базы данных или упрощения разработки, но современные базы данных вовсе не подвержены влиянию нескольких таблиц, и разработка может стать излишне сложной.Более того, второй подход позволяет расширить функциональность отношений, добавляя дополнительные свойства к каждой таблице соединений отдельно.И для 6 сущностей, с которыми вам приходится иметь дело, мне кажется более безопасным перейти ко второму подходу.Я использовал этот подход в приложении со многими типами файлов и взаимосвязями, и он всегда был очень простым и расширяемым.Эти дополнительные свойства в каждой таблице relashion тоже оказались очень полезными.

Надеюсь, я смогу помочь, веселое кодирование!

0 голосов
/ 04 октября 2018

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

Это было бы отношение один ко многим, если (например) Document было несколько (например, список) Version с.

Если вы хотите, чтобы несколько сущностей ссылались на один и тот же тип сущности, вы могли бы явно разместить внешние ключи в классах Document и Register:

class Document
{
    public Version DocumentVersion { get; set; }
    public int DocumentVersionId { get; set; } // Or whatever datatype your ID is
    // Other properties
}

class Register
{
    public Version RegisterVersion { get; set; }
    public int RegisterVersionId { get; set; } // Or whatever datatype your ID is
    // Other properties
}

class Version
{
    public int VersionNumber { get; set; }
    // Other properties
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...