Создание модели предметной области без циклических ссылок в Entity Framework

StackOverflow https://stackoverflow.com/questions/19840537

  •  29-07-2022
  •  | 
  •  

Вопрос

Я нашел решение, которое работает (с использованием DTO и AutoMapper), которое воспроизведено ниже, но я бы предпочел ответ, в котором перечислены разные подходы к проблеме с примерами, и это будет помечено как ответ, если будет получено.

В моей модели сущности у меня есть свойство навигации, которое переходит от дочернего объекта к родительскому объекту.Мой проект шел гладко.Затем я начал использовать AutoFixture для модульного тестирования, и тестирование завершилось неудачей, AutoFixture сказал, что у меня была циклическая ссылка.

Теперь я понимаю, что подобные свойства навигации по циклическим ссылкам допустимы в Entity Framework, но я нашел этот пост (Используйте значение родительского свойства при создании сложного дочернего элемента в AutoFixture), где Марк Симанн, создатель AutoFixture, заявляет:

"Для справки, я уже много лет не писал API с циклической ссылкой, так что вполне возможно избежать этих отношений Родитель / потомок".

Итак, я хочу понять, КАК можно реорганизовать модель предметной области, чтобы избежать отношений дочерний / родительский.

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

Примечание:Свойство, вызывающее циклическую ссылку, является User в модели UserTeam .

Модели:

public class UserProfile
{
    public UserProfile()
    {
        UserTeams = new HashSet<UserTeam>();
        Games = new HashSet<Game>();
    }

    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int UserId { get; set; }
    public string UserName { get; set; }       

    public virtual ICollection<UserTeam> UserTeams { get; set; }
    public virtual ICollection<Game> Games { get; set; }
}


public class Game
{
    public Game()
    {
        UserTeams = new HashSet<UserTeam>();
    }

    public int Id { get; set; }
    public int CreatorId { get; set; }

    public virtual ICollection<UserTeam> UserTeams { get; set; }
}


public class UserTeam
{
    public UserTeam()
    {
        UserTeam_Players = new HashSet<UserTeam_Player>();
    }

    public int Id { get; set; }
    public int UserId { get; set; }
    public int GameId { get; set; }

    public virtual UserProfile User { get; set; }
    public virtual ICollection<UserTeam_Player> UserTeam_Players { get; set; }
}

Метод Репозитария

public IEnumerable<Game> GetAllGames()
    {
        using (DataContext)
        {             
            var _games = DataContext.Games
                 .Include(x => x.UserTeams)
                 .Include(x => x.UserTeams.Select(y => y.User))
                 .ToList();
            if (_games == null)
            {
                // log error
                return null;
            }
            return _games;
        }
    }

Вид

@model IEnumerable<Game>
@foreach (var item in Model){
    foreach (var userteam in item.UserTeams){
        <p>@userteam.User.UserName</p>
    }
}

Теперь, если я удалю свойство навигации 'User', я не смогу выполнить '@userteam.User.UserName'

Итак, как мне провести рефакторинг модели домена, чтобы удалить циклическую ссылку, имея при этом возможность легко перебирать игры, и сделать что-то вроде UserTeam.User.Имя пользователя?

Это было полезно?

Решение

У меня был аналогичная проблема с AutoFixture и EntityFramework некоторое время назад.Мое решение состояло в том, чтобы добавить расширение к AutoFixture, которое позволяет вам создавать SUT с несколькими рекурсиями.Это расширение недавно было внедрено в AutoFixture.

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

Во-первых, у вас есть древовидные или графические структуры.Здесь все, что угодно, кроме рекурсии, означало бы косвенное использование слабо связанных идентификаторов узлов.Вместо определения ассоциации вам пришлось бы просматривать дерево запрос за запросом или кэшировать все это целиком и выполнять поиск по ключу узла, что может быть непрактичным в зависимости от размера дерева.Здесь очень удобно заставить EF выполнять всю работу за вас.

Другая распространенная структура - это двусторонняя навигационная структура, аналогичная вашему пользовательскому / игровому сценарию.Здесь часто бывает не так уж и неудобно ограничить навигационный поток в одном направлении.Если вы опустите одно направление, скажем, от игры к команде, вы все равно сможете легко запросить все команды для данной игры.Итак:У пользователя есть список игр и список команд.У команды есть список игр.Игры не имеют навигационных ссылок ни на те, ни на другие.Чтобы привлечь всех пользователей к определенной игре, вы могли бы написать что-то вроде:

var users = (from user in DataContext.Users
            from game in user.Games
            where game.Name == 'Chess'
            select user).Distinct()

Другие советы

Я нашел решение, которое работает (с использованием DTO и AutoMapper), которое воспроизведено ниже, но я все же предпочел бы ответ, в котором перечислены разные подходы к проблеме с примерами, в частности, является ли это желательным решением, или я должен придерживаться свойств навигации такими, какими они были, избавиться от автоматической фиксации, и когда дело доходит до сериализации для json, просто используйте другие обходные пути (атрибуты и т.д.)...

Итак, в моей модели просмотра я добавил пару классов:

public class GameDTO
{
    public int Id { get; set; }
    public int CreatorId { get; set; }

    public ICollection<UserTeamDTO> UserTeamsDTO { get; set; }
}

public class UserTeamDTO : UserTeam
{
    public UserProfile User { get; set; }
}

И в моем контроллере я использую AutoMapper для сопоставления объектов Game / UserTeam из репозитория с моими объектами DTO и возвращаю IList _gamesDto в представление.

var _games = _gameRepository.GetAllGames();

IList<GameDTO> _gamesDto = new List<GameDTO>();
IList<UserTeamDTO> _userteamsDto = new List<UserTeamDTO>();
GameDTO _gameDto = new GameDTO();
UserTeamDTO _userteamDto = new UserTeamDTO();
Mapper.CreateMap<Game, GameDTO>();
Mapper.CreateMap<UserTeam, UserTeamDTO>();

foreach (Game _game in _games)
{
    foreach (UserTeam _userteam in _game.UserTeams)
    {
        _userteamDto = Mapper.Map<UserTeamDTO>(_userteam);
        _userteamDto.User = _userRepository.GetUser(_userteam.UserId);
        _userteamsDto.Add(_userteamDto);
    }

    _gameDto = Mapper.Map<GameDTO>(_game);
    _gameDto.UserTeamsDTO = _userteamsDto;
    _gamesDto.Add(_gameDto);
}

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

Сначала я удалил избыточные навигационные свойства, которые создавали циклические ссылки.Я позаботился о том, чтобы мое результирующее дерево данных имело смысл.Это позволило мне прояснить, какие объекты владеют какими отношениями.

Это также лишило EF возможности автоматически рассуждать о моих отношениях.Мне пришлось указать отношения "Один ко многим" и "Многие ко многим", используя FluentAPI.Я нашел решение здесь: https://stackoverflow.com/a/16719203/1887885

Надеюсь, это будет полезно.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top