C# 如何首先在EF代码中为一个数据库处理在多个db上下文中使用的一个类?

C# 如何首先在EF代码中为一个数据库处理在多个db上下文中使用的一个类?,c#,entity-framework,design-patterns,ef-code-first,dbcontext,C#,Entity Framework,Design Patterns,Ef Code First,Dbcontext,我正在尝试制作一些在我的存储库层中使用EF的模块类库API。为了实现这些功能,我需要在每个类库中都有一个dbcontext类。但是当我需要在每个模块中引用一个类时会发生什么呢?例如,我有一个用户模块,其数据库上下文包括: 使用者 团体 角色 然后我有一个位置模块,包括: 建筑物 地点 房间 然后是第三个设备模块,该模块具有: 装备 设备类型 工作指令 后两者仍然需要对用户的引用,这对于每个模块来说都是不可或缺的。但是我不能将两个单独的用户类添加到指向同一个db的两个上下文中,它们可能

我正在尝试制作一些在我的存储库层中使用EF的模块类库API。为了实现这些功能,我需要在每个类库中都有一个dbcontext类。但是当我需要在每个模块中引用一个类时会发生什么呢?例如,我有一个用户模块,其数据库上下文包括:

  • 使用者
  • 团体
  • 角色
然后我有一个位置模块,包括:

  • 建筑物
  • 地点
  • 房间
然后是第三个设备模块,该模块具有:

  • 装备
  • 设备类型
  • 工作指令
后两者仍然需要对用户的引用,这对于每个模块来说都是不可或缺的。但是我不能将两个单独的用户类添加到指向同一个db的两个上下文中,它们可能会变得不同步。因此,显而易见的解决方案就是让后两个模块需要用户模块,而这些模块中需要用户的任何类都只引用userID。但这会破坏规范化,因为它不是外键,所以我不确定这个想法有多好

我突然想到的另一种可能性是让每个模块的dbcontext使用一个接口,并允许使用该模块的人声明自己的dbcontext并实现所有这些成员,但我不确定这是否可行


我基本上只想让一组类库模块对其他程序员可用,这些模块定义一组通用的类和API调用,同时使用EF作为基础,目的是将所有这些都存储在一个DB中。但我不太确定如何通过DbContexts的工作来实现这一点。当您有多个模块需要相同的对象时会发生什么情况?

您所表示的三个上下文通常与中设计的匹配,正如Steeve正确指出的那样

显然,有多种方法可以实现此场景,每种方法都有其优缺点

我建议使用两种方法来尊重领域驱动设计的最佳实践,并具有很大的灵活性

方法#1:软分离

我在第一个有界上下文中定义一个
User
类,在第二个有界上下文中定义一个表示用户引用的接口

让我们定义用户:

class User
{
    [Key]
    public Guid Id { get; set; }

    public string Name { get; set; }
}
参考用户实施的其他型号
IUserRelated

interface IUserRelated
{
    [ForeignKey(nameof(User))]
    Guid UserId { get; }
}
设计模式建议不要直接从两个分离的有界上下文链接两个实体,而是存储它们各自的引用

建筑
类看起来像:

class Building : IUserRelated
{
    [Key]
    public Guid Id { get; set; }

    public string Location { get; set; }
    public Guid UserId { get; set; }
}
如您所见,
建筑
模型只知道
用户的引用
。尽管如此,该接口仍充当外键,并约束插入到此
UserId
属性中的值

现在让我们定义数据库上下文

class BaseContext<TContext> : DbContext where TContext : DbContext
{
    static BaseContext()
    {
        Database.SetInitializer<TContext>(null);
    }

    protected BaseContext() : base("Demo")
    {

    }
}

class UserContext : BaseContext<UserContext>
{
    public DbSet<User> Users { get; set; }
}

class BuildingContext : BaseContext<BuildingContext>
{
    public DbSet<Building> Buildings { get; set; }
}
方法#2:硬分离

我在每个有界上下文中定义一个
User
类。接口强制执行公共属性。Martin Fowler对该方法进行了如下说明:

用户绑定上下文:

public class User : IUser
{
    [Key]
    public Guid Id { get; set; }

    public string Name { get; set; }
}

public class UserContext : BaseContext<UserContext>
{
    public DbSet<User> Users { get; set; }
}
public class User : IUser
{
    [Key]
    public Guid Id { get; set; }
}

public class Building
{
    [Key]
    public Guid Id { get; set; }

    public string Location { get; set; }

    public virtual User User { get; set; }
}

public class BuildingContext : BaseContext<BuildingContext>
{
    public DbSet<Building> Buildings { get; set; }

    public DbSet<User> Users { get; set; }
}
使用EF迁移非常简单。用户绑定上下文的迁移脚本(由EF生成):

建筑边界上下文的迁移脚本(由EF生成)。我必须删除表
Users
的创建,因为其他绑定上下文有责任创建它。在为模块化方法创建表之前,您仍然可以检查表是否不存在:

public partial class Initial : DbMigration
{
    public override void Up()
    {
        CreateTable(
            "dbo.Buildings",
            c => new
                {
                    Id = c.Guid(nullable: false),
                    Location = c.String(),
                    User_Id = c.Guid(),
                })
            .PrimaryKey(t => t.Id)
            .ForeignKey("dbo.Users", t => t.User_Id)
            .Index(t => t.User_Id);
    }

    public override void Down()
    {
        DropForeignKey("dbo.Buildings", "User_Id", "dbo.Users");
        DropIndex("dbo.Buildings", new[] { "User_Id" });
        DropTable("dbo.Users");
        DropTable("dbo.Buildings");
    }
}
对这两个上下文应用
升级数据库
,您的数据库就准备好了

编辑有关在类中添加新属性的OP请求
User

当一个有界上下文向类
User
添加一个新属性时,它会在引擎盖下递增地添加一个新列。它不会重新定义整个表。这就是为什么这个实现也是非常通用的

下面是迁移脚本的一个示例,其中在有界上下文
建筑
中,向类
用户
添加了一个新属性
认证

public partial class Accreditation : DbMigration
{
    public override void Up()
    {
        AddColumn("dbo.Users", "Accreditation", c => c.String());
    }

    public override void Down()
    {
        DropColumn("dbo.Users", "Accreditation");
    }
}

不确定这是否正是您所需要的,但请参阅这里引用的Lehrman的文章:这个答案看起来很有趣,但我可以预见当他们尝试为每个上下文更新迁移时会出现问题。也许这样做是可行的。她通过文章中提到的“优步模式”解决了这个问题。其他上下文不进行迁移。这种方法的问题是它仍然需要第三个总体的“一体式”上下文。在上执行迁移。这种方式打破了挑选和选择你想要的东西的模式。您仍然需要创建自己的上下文并将所有对象粘贴到其中。这不会太糟糕,除非你已经有了其他特定的上下文。嘿@DOTang,我编辑了我的答案,提供了一种硬分离的方法,使你能够自由地使用EF迁移脚本。嘿,这看起来很好,但是,您将如何处理诸如建筑模块的用户具有不在用户模块中的属性和/或关系之类的情况?我假设它仍然可以工作,但是您必须在用户模块之后在建筑模块上运行迁移,但是如果您有第三个模块也引用了用户,并且在用户模块上也需要它自己的特殊属性,那么它似乎会崩溃?反过来,答案是再次使用庞大的总体上下文,对吗?您所描述的案例通过硬分离方法得到了完美的处理。在建筑边界上下文中绑定到用户的任何附加属性必须插入到建筑边界上下文中的类
user
。如果第三个模块还需要为用户拥有一些其他属性,那么这个第三个模块将有一个类
    // Defines some constants
    const string userName = "James";
    var userGuid = Guid.NewGuid();

    // Create a user
    using (var userContext = new UserContext())
    {
        userContext.Users.Add(new User { Name = userName, Id = userGuid });
        userContext.SaveChanges();
    }

    // Create a building linked to a user
    using (var buildingContext = new BuildingContext())
    {
        var userReference = buildingContext.Users.First(user => user.Id == userGuid);

        buildingContext.Buildings.Add(new Building { Id = Guid.NewGuid(), Location = "Switzerland", User = userReference });
        buildingContext.SaveChanges();
    }
public partial class Initial : DbMigration
{
    public override void Up()
    {
        CreateTable(
            "dbo.Users",
            c => new
                {
                    Id = c.Guid(nullable: false),
                    Name = c.String(),
                })
            .PrimaryKey(t => t.Id);

    }

    public override void Down()
    {
        DropTable("dbo.Users");
    }
}
public partial class Initial : DbMigration
{
    public override void Up()
    {
        CreateTable(
            "dbo.Buildings",
            c => new
                {
                    Id = c.Guid(nullable: false),
                    Location = c.String(),
                    User_Id = c.Guid(),
                })
            .PrimaryKey(t => t.Id)
            .ForeignKey("dbo.Users", t => t.User_Id)
            .Index(t => t.User_Id);
    }

    public override void Down()
    {
        DropForeignKey("dbo.Buildings", "User_Id", "dbo.Users");
        DropIndex("dbo.Buildings", new[] { "User_Id" });
        DropTable("dbo.Users");
        DropTable("dbo.Buildings");
    }
}
public partial class Accreditation : DbMigration
{
    public override void Up()
    {
        AddColumn("dbo.Users", "Accreditation", c => c.String());
    }

    public override void Down()
    {
        DropColumn("dbo.Users", "Accreditation");
    }
}