使用 EF 正确实现 Repository

3

我正在尝试使用持久层和领域模型来实现仓储。我的主要目标是将领域逻辑与其他层分离,例如洋葱架构。

作为我的存储库,我使用Entity Framework 6.2。为了映射模型,我使用Automapper。

我不想使用实体模型作为我的领域模型。

我已经阅读了很多关于DDD中仓储概念和思想的文章,并浏览了许多示例,但没有一个给我答案如何处理更复杂的模型。

例如,在我的控制台应用程序中,我有一个AppUser,其中包含BankAccounts。当我向用户添加新的BankAccount时,我会得到一行额外的未被跟踪的BankAccount实体。这是因为在从领域模型映射到持久化模型(即实体)时,EF无法确定哪些BankAccounts被跟踪并将它们全部视为新的。

之前的版本:

+------------------------------------+
|Id |BankAccountNumber   |AppUser_Id |
+------------------------------------+
|1  |Seeded Bank Account |  1        |
+------------------------------------+

之后

+------------------------------------+
|Id |BankAccountNumber   |AppUser_Id |
+------------------------------------+
|1  |Seeded Bank Account |  NULL     |
+------------------------------------+
|2  |Added Bank Account  |  1        |
+------------------------------------+
|3  |Seeded Bank Account |  1        |
+------------------------------------+

这是我的代码。

class Program
{
    static void Main(string[] args)
    {
        CreateMappings();

        var appUserRepository = new AppUserRepository();

        var appUser = appUserRepository.Get(1);

        var bankAccount = new BankAccountDM
        {
            BankAccountNumber = "Added Bank Account"
        };

        var service = new AppUserDomainSerivce(appUserRepository);
        service.AddBankAccount(bankAccount, appUser);

        Console.WriteLine("== END ==");
        Console.ReadKey();
    }

    public static void CreateMappings()
    {
        // Config Automapper
        Mapper.CreateMap<AppUserDM, AppUser>()
            .ForMember(x => x.Id, opt => opt.MapFrom(y => y.Id))
            .ForMember(x => x.Name, opt => opt.MapFrom(y => y.Name))
            .ForMember(x => x.BankAccounts, opt => opt.MapFrom((y => y.BankAccounts)))
            .ReverseMap();

        Mapper.CreateMap<BankAccountDM, BankAccount>()
            .ReverseMap();
    }

    public class AppUserDomainSerivce
    {
        private readonly AppUserRepository _appUserRepository;

        public AppUserDomainSerivce(AppUserRepository appUserRepository)
        {
            _appUserRepository = appUserRepository;
        }

        public void AddBankAccount(BankAccountDM bankAccount, AppUserDM appUser)
        {
            appUser.BankAccounts.Add(bankAccount);

            // Save changes
            _appUserRepository.Save(appUser);
        }
    }

    public class AppUserRepository
    {
        public AppUserDM Get(int id)
        {
            using (var ctx = new AppContext())
            {
                var userEntity = GetAppUser(id, ctx);
                return Mapper.Map<AppUserDM>(userEntity);
            }
        }

        public void Save(AppUserDM user)
        {
            // Save entity in storage
            using (var ctx = new AppContext())
            {
                var userEntity = GetAppUser(user.Id, ctx);
                userEntity = Mapper.Map(user, userEntity);

                ctx.SaveChanges();
            }
        }

        private AppUser GetAppUser(int id, AppContext ctx)
        {
            return ctx.AppUsers
                .Include("BankAccounts")
                .SingleOrDefault(x => x.Id == id);
        }
    }
}


// Entities and Domain Models

public class BankAccountDM
{
    public int Id { get; set; }
    public string BankAccountNumber { get; set; }
}

public class BankAccount
{
    [Key]
    public int Id { get; set; }
    public string BankAccountNumber { get; set; }
}

public class AppUserDM
{
    public int Id { get; set; }
    public List<BankAccountDM> BankAccounts { get; set; }
    public string Name { get; set; }
}

public class AppUser
{
    [Key]
    public int Id { get; set; }
    public virtual ICollection<BankAccount> BankAccounts { get; set; }
    public string Name { get; set; }
}


// Entity DbContext

public class AppContext : DbContext
{
    public AppContext() : base("AppContext")
    {
    }

    public DbSet<AppUser> AppUsers { get; set; }
}

public Configuration()
{
    AutomaticMigrationsEnabled = true;
}

protected override void Seed(ConsoleApplication1.AppContext context)
{
    context.AppUsers.Add(new AppUser()
    {
        // This AppUser will get ID = 1 once saved into DB
        Name = "My Test User",
        BankAccounts = new List<BankAccount>()
        {
            new BankAccount()
            {
                BankAccountNumber = "Seeded Bank Account"
            }
        }
    });
}

个人而言,我不太关心EF。我直接将实体序列化为json。恢复很容易,只需反序列化对象即可。然而,我会发布事件,并且我有事件处理程序创建/更新读模型,在那里您可以广泛使用EF(在其最佳状态下:使用数据结构进行crud操作)。顺便说一句,您的“领域”也是CRUDy的,您只有数据结构。如果您的测试过于简单,则不需要高级功能。将简单用例强制转换为适用于复杂用例的解决方案是适得其反的。 - MikeSW
我想了解一下如何将数据序列化为JSON格式。听起来相当简单且令人鼓舞。你能提供一些链接吗? 是的,这个领域很简单,介绍领域驱动设计可能不是最好的选择。这只是一个玩耍的场所,用于获得一些经验,而不是真正的商业项目。 - pizycki
1个回答

0

如果我使用您的方法(没有事件溯源); 在存储库的保存方法中,我通常会从数据库中读取整个聚合,然后与给定的聚合进行比较。

当然,我们会检查从数据库中读取的聚合版本是否与我们要保存的聚合相同。因此,存储库中的单个保存方法可以处理域实体和值对象中的添加和删除。

或者您还可以在每个聚合-实体-值对象中保存状态(新建、修改、已删除等)。

关于将聚合作为JSON存储; 这篇来自Vaugh Vernon的帖子可能是一个有用的起点。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接