15

我找到了一个可行的解决方案(使用 DTO 和 AutoMapper),如下所示,但我更喜欢列出解决问题的不同方法和示例的答案,如果收到,这将被标记为答案。

在我的实体模型中,我有一个从子实体到父实体的导航属性。我的项目进展顺利。然后我开始使用 AutoFixture 进行单元测试,测试失败,AutoFixture 说我有循环引用。

现在,我意识到像这样的循环引用导航属性在 Entity Framework 中是可以的,但是我发现了这篇文章(在 AutoFixture 中创建复杂子项时使用父属性的值),其中 AutoFixture 的创建者 Mark Seemann 指出:

“为了记录,我已经好几年没有写过一个带有循环引用的 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>
    }
}

现在,如果我删除“用户”导航属性,我将无法执行“@userteam.User.UserName”

那么,如何重构域模型以删除循环引用,同时能够轻松地循环游戏并执行 UserTeam.User.Username 之类的操作?

4

3 回答 3

6

不久前,我在AutoFixture 和 EntityFramework 上遇到了类似的问题。我的解决方案是向 AutoFixture 添加一个扩展,它允许您使用一些递归构建一个 SUT。该扩展最近已在 AutoFixture 中采用。

但是我知道您的问题不是关于如何使 AutoFixture 构造递归数据结构,这确实是可能的,而是如何在没有递归的情况下创建域模型。

首先,你有树或图结构。这里除了递归之外的任何东西都意味着通过松散耦合的节点ID进行间接。您不必定义关联,而是必须逐个查询遍历树或缓存整个事物并通过节点键查找进行遍历,这取决于树的大小,这可能是不切实际的。在这里,让 EF 为您完成工作非常方便。

另一种常见的结构是类似于您的用户/游戏场景的双向导航结构。在这里,将导航流修剪到单个方向通常不是那么不方便。如果你省略了一个方向,比如从一场比赛到另一支球队,你仍然可以轻松地查询给定比赛的所有球队。所以:用户有一个游戏列表和一个团队列表。团队有一个游戏列表。游戏也没有导航参考。要获得特定游戏的所有用户,您可以编写如下内容:

var users = (from user in DataContext.Users
            from game in user.Games
            where game.Name == 'Chess'
            select user).Distinct()
于 2013-11-11T09:17:25.227 回答
1

我最近遇到了一个类似的问题,这也影响了 JSON 对象的序列化。我决定从我的数据模型中删除循环引用。

我首先删除了创建循环引用的冗余导航属性。我确保我生成的数据树是有意义的。这使我能够明确哪些对象拥有哪些关系。

这也使 EF 无法自动推理我的关系。我必须使用 FluentAPI 指定一对多和多对多关系。我在这里找到了解决方案:https ://stackoverflow.com/a/16719203/1887885

希望这会有所帮助。

于 2015-03-24T15:35:02.207 回答
1

我找到了一个可行的解决方案(使用 DTO 和 AutoMapper),如下所示,但我仍然希望通过示例列出解决问题的不同方法的答案,特别是这是否是一个理想的解决方案,或者我是否应该坚持原来的导航属性,摆脱 AutoFixture,当涉及到 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);
}
于 2013-11-07T17:37:24.510 回答