在实体框架中创建没有循环引用的域模型

问题描述 投票:15回答:4

我已经找到了一个可行的解决方案(使用DTO和AutoMapper),下面将对此进行复制,但是我希望给出一个答案,其中列出了解决问题的不同方法,并带有示例,并且将其标记为如果收到答案。

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

现在,我意识到在实体框架中可以使用这样的循环引用导航属性,但是我发现了这篇文章(Use value of a parent property when creating a complex child in AutoFixture),其中AutoFixture的创建者Mark Seemann指出:

“为了记录,多年来我一直没有编写具有循环引用的API,因此很可能避免那些父子关系。”

因此,我想了解如何重构域模型以避免子/父关系。

下面是有问题的实体类,存储库方法以及如何使用导致View中循环引用的属性。 完美的答案将通过示例说明我可以选择的不同选项,以及每种方法的基本优点/缺点。

注意:引起循环引用的属性是UserTeam模型中的User。

型号:

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.Username?

entity-framework autofixture
4个回答
6
投票
不久前我有一个带有AutoFixture和EntityFramework的similar problem。我的解决方案是为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()


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返回到View。

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


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

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

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

希望这会有所帮助。


0
投票
尝试使用RepeatCount属性

var objFixture =新的Fixture();

objFixture.RepeatCount = 0;

© www.soinside.com 2019 - 2024. All rights reserved.