Création d'un modèle de domaine sans références circulaires dans le cadre d'entité

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

  •  29-07-2022
  •  | 
  •  

Question

J'ai trouvé une solution qui fonctionne (en utilisant DTOS et Automapper), qui est reproduite ci-dessous, mais je préférerais une réponse qui répertorie le différentes approches du problème avec des exemples et cela sera marqué comme la réponse s'il est reçu.

Dans mon modèle d'entité, j'ai une propriété de navigation qui passe d'une entité enfant à l'entité parent. Mon projet fonctionnait à la nage. Ensuite, j'ai commencé à utiliser l'autofixture pour les tests unitaires et les tests ont échoué, automatiquement en disant que j'avais une référence circulaire.

Maintenant, je me rends compte que les propriétés de navigation de référence circulaire comme celle-ci sont OK dans l'entité Framework, mais j'ai trouvé ce post (Utiliser la valeur d'une propriété parent lors de la création d'un enfant complexe dans Autofixture), où Mark Seemann, le créateur de l'autofixture déclare:

"Pour mémoire, je n'ai pas écrit une API avec une référence circulaire depuis des années, il est donc tout à fait possible d'éviter ces relations parents / enfants."

Donc, je veux comprendre comment un modèle de domaine peut être refactorisé pour éviter les relations enfants / parents.

Vous trouverez ci-dessous les classes d'entités en question, la méthode du référentiel et comment j'utilise la propriété provoquant la référence circulaire à mon avis. La réponse parfaite expliquerait les différentes options que je pourrais choisir avec des exemples et les avantages / inconvénients de base de chaque approche.

Remarque: La propriété provoquant la référence circulaire est l'utilisateur, dans le modèle Userateam.

Des modèles:

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éthode de référentiel

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

Voir

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

Maintenant, si je supprime la propriété de navigation 'utilisateur', je ne pourrais pas faire '@ userateam.user.userName'

Alors, comment puis-je refactor le modèle de domaine pour supprimer la référence circulaire, tout en étant facilement en parcourant les jeux, et faire quelque chose comme userat.User.Username?

Était-ce utile?

La solution

J'ai eu un problème similaire avec Autofixture et EntityFramework il y a quelque temps. Ma solution était d'ajouter une extension à AutoFixture, qui vous permet de construire un SUT avec quelques récursions. Cette extension a récemment été adoptée dans Autofixture.

Mais je comprends que votre question ne concernait pas comment faire de la construction de structures de données récursives de construction automatique, ce qui est en effet possible, mais comment créer des modèles de domaine sans récursivité.

Tout d'abord, vous avez des structures d'arbres ou de graphiques. Ici, tout sauf la récursivité signifierait l'indirection à travers des identifiants de nœuds couplés en vrac. Au lieu de définir une association, vous devrez traverser la requête par arborescence ou cache le tout et traverser par la recherche de nœud, ce qui peut être peu pratique en fonction de la taille de l'arbre. Ici, il est très pratique de faire faire le travail pour vous.

L'autre structure commune est une structure de navigation bidirectionnelle similaire à votre scénario utilisateur / jeu. Ici, il n'est souvent pas si gênant de tailler le flux de navigation vers une seule direction. Si vous omettez une direction, disons d'un jeu à l'autre, vous pouvez toujours interroger facilement toutes les équipes pour un jeu donné. Donc: l'utilisateur a une liste de jeux et une liste d'équipes. L'équipe a une liste de jeux. Les jeux n'ont aucune référence à la navigation à l'un ou aux deux. Pour obtenir tous les utilisateurs pour un jeu spécifique, vous pouvez écrire quelque chose comme:

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

Autres conseils

J'ai trouvé une solution qui fonctionne (en utilisant DTOS et Automapper), qui est reproduite ci-dessous, mais je préférerais toujours une réponse qui répertorie le différentes approches du problème Avec des exemples, en particulier s'il s'agit d'une solution souhaitable, ou si je dois m'en tenir aux propriétés de navigation telles qu'elles étaient, débarrassez-vous de l'autofixture, et en ce qui concerne le sérialisation de JSON, utilisez simplement d'autres travaux (attributs etc.) ...

Donc, dans mon modèle de vue, j'ai ajouté quelques classes:

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

Et dans mon contrôleur, j'utilise Automapper pour cartographier les objets Game / Useram du référentiel à mes objets DTO, et renvoyez l'ILIST _Gamesdto à la vue.

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

J'ai eu un problème similaire récemment qui a également eu un impact sur la sérialisation des objets JSON. J'ai décidé de supprimer les références circulaires de mon modèle de données.

J'ai d'abord supprimé les propriétés de navigation redondantes qui créaient les références circulaires. Je me suis assuré que mon arbre de données résultant avait du sens. Cela m'a permis de montrer clairement quels objets possèdent quelles relations.

Cela a également rendu EF incapable de raisonner automatiquement sur mes relations. J'ai dû spécifier les relations un à plusieurs et plusieurs à plusieurs en utilisant le FluentAPI. J'ai trouvé une solution ici: https://stackoverflow.com/a/16719203/1887885

J'espère que cela est utile.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top