Pergunta

Encontrei uma solução que funciona (usando DTOs e AutomApper), que é reproduzida abaixo, mas eu preferiria uma resposta que lista o diferentes abordagens para o problema com exemplos e isso será marcado como resposta se recebido.

No meu modelo de entidade, tenho uma propriedade de navegação que passa de uma entidade filho para a entidade pai. Meu projeto estava funcionando mal. Então comecei a usar o AutoFixture para testes de unidade e o teste falhou, com a combinação automática dizendo que eu tinha uma referência circular.

Agora, percebo que propriedades circulares de navegação como essa são boas na estrutura da entidade, mas encontrei este post (Use o valor de uma propriedade pai ao criar uma criança complexa no AutoFixture), onde Mark Seemann, o criador do Autofixture afirma:

"Para constar, não escrevi uma API com uma referência circular há anos, por isso é bem possível evitar essas relações entre pais/filhos".

Então, quero entender como um modelo de domínio pode ser reformado para evitar relações infantis/pais.

Abaixo estão as classes de entidade em questão, o método do repositório e como uso a propriedade que causa a referência circular na minha opinião. A resposta perfeita explicaria as diferentes opções que eu poderia escolher com exemplos e os prós/contras básicos de cada abordagem.

Nota: A propriedade que causa a referência circular é usuário, no modelo UserTeam.

Modelos:

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; }
}

Método do repositório

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;
        }
    }

Visão

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

Agora, se eu remover a propriedade de navegação 'Usuário', não seria capaz de fazer '@userTeam.user.username'

Então, como refatorar o modelo de domínio para remover a referência circular, enquanto é capaz de percorrer os jogos facilmente e fazer algo como userTeam.User.username?

Foi útil?

Solução

eu tive um Problema semelhante com Autofixture e EntityFramework há um tempo. Minha solução foi adicionar uma extensão à manutenção automática, que permite construir um SUT com algumas recursões. Essa extensão foi adotada recentemente no Autofixture.

Mas entendo que sua pergunta não era sobre como tornar as estruturas de dados recursivas de construção automática, o que é realmente possível, mas como criar modelos de domínio sem recursão.

Primeiro, você tem estruturas de árvore ou gráfico. Aqui tudo, menos a recursão, significaria indireção por meio de IDs de nós soltos acoplados. Em vez de definir uma associação, você teria que atravessar a consulta de árvore ou cache a coisa toda e atravessar a pesquisa da tecla de nó, o que pode ser impraticável, dependendo do tamanho da árvore. Aqui é muito conveniente fazer o EF fazer o trabalho para você.

A outra estrutura comum é uma estrutura de navegação bidirecional semelhante ao seu cenário de usuário / jogo. Aqui geralmente não é tão inconveniente podar o fluxo de navegação para uma única direção. Se você omitir uma direção, digamos do jogo para o time, ainda poderá consultar facilmente todas as equipes para um determinado jogo. Então: o usuário tem uma lista de jogos e uma lista de equipes. A equipe tem uma lista de jogos. Os jogos não têm referência de navegação a nenhum deles. Para obter todos os usuários para um jogo específico, você pode escrever algo como:

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

Outras dicas

Encontrei uma solução que funciona (usando DTOs e AutomApper), que é reproduzida abaixo, mas eu ainda preferiria uma resposta que lista o diferentes abordagens para o problema Com exemplos, em particular se essa é uma solução desejável, ou se eu deveria seguir as propriedades de navegação como eram, livrar -se da manutenção automática e, quando se trata de serializar para o JSON, apenas utiliza outros trabalhos (atributos etc.) ...

Então, no meu modelo de visão, adicionei algumas aulas:

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; }
}

E no meu controlador, uso o AutomApper para mapear os objetos Game / UserTeam do repositório para meus objetos DTO e devolver o ilista _GamesDTO à exibição.

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);
}

Recentemente, tive um problema semelhante, o que também impactou os objetos JSON em série. Decidi remover as referências circulares do meu modelo de dados.

Eu removi pela primeira vez as propriedades redundantes de navegação que estavam criando as referências circulares. Eu certifiquei -me de que minha árvore de dados resultante fizesse sentido. Isso me permitiu deixar claro quais objetos possuem quais relacionamentos.

Isso também tornou a EF incapaz de raciocinar automaticamente sobre meus relacionamentos. Eu tive que especificar os relacionamentos um para muitos e muitos para muitos usando o Fluentapi. Encontrei uma solução aqui: https://stackoverflow.com/a/16719203/1887885

Espero que isso seja útil.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top