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

标签 c# entity-framework design-patterns ef-code-first dbcontext

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

  • 用户
  • 团体
  • 角色

然后我有一个位置模块,其中包括:

  • 建筑物
  • 地点
  • 房间

然后说第三个设备模块,它有:

  • 设备
  • 设备类型
  • 工单

后两者仍然需要对用户的引用,这几乎是每个模块不可或缺的一部分。但是我不能将两个单独的用户类添加到指向同一个数据库的两个上下文中,它们可能会变得不同步。因此,显而易见的解决方案就是让后两个模块需要用户模块,而那些需要用户的模块中的任何类都只引用用户 ID。这会破坏规范化,因为它不是外键,所以我不确定这个想法有多好。

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

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

最佳答案

您所代表的三个上下文通常匹配 Bounded Contexts按照 Domain-driven design 中的设计,正如 Steeve 正确指出的那样。

显然有多种方法可以实现这种情况,每种方法都各有利弊。

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

方法 #1:软分离

我在第一个限界上下文中定义了一个 User 类,在第二个限界上下文中定义了一个表示对用户的引用的接口(interface)。

让我们定义用户:

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

    public string Name { get; set; }
}

其他引用用户的模型实现IUserRelated:

interface IUserRelated
{
    [ForeignKey(nameof(User))]
    Guid UserId { get; }
}

设计模式建议不要直接链接来自两个分离的有界上下文的两个实体,而是存储它们各自的引用。

Building 类如下所示:

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

    public string Location { get; set; }
    public Guid UserId { get; set; }
}

如您所见,Building 模型只知道 User 的引用。尽管如此,该接口(interface)充当外键并限制插入到此 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; }
}

以及初始化数据库的数据库上下文:

class DatabaseContext : DbContext
{
    public DbSet<Building> Buildings { get; set; }
    public DbSet<User> Users { get; set; }

    public DatabaseContext() : base("Demo")
    {
    }
}

最后,创建用户和建筑物的代码:

// Defines some constants
const string userName = "James";
var userGuid = Guid.NewGuid();

// Initialize the db
using (var db = new DatabaseContext())
{
    db.Database.Initialize(true);
}

// 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())
{
    buildingContext.Buildings.Add(new Building {Id = Guid.NewGuid(), Location = "Switzerland", UserId = userGuid});
    buildingContext.SaveChanges();
}

方法 #2:硬分离

我在每个限界上下文中定义了一个 User 类。接口(interface)强制执行公共(public)属性。 Martin Fowler 说明了这种方法,如下所示:

enter image description here

用户限界上下文:

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

在这种情况下,在 BuildingContext 中拥有一个 Users 属性是完全可以接受的,因为用户也存在于建筑物的上下文中。

用法:

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

使用 EF 迁移非常简单。用户限界上下文的迁移脚本(由 EF 生成):

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

构建限界上下文的迁移脚本(由 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");
    }
}

为这两个上下文应用Upgrade-Database,您的数据库就准备好了!

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

当有界上下文向类 User 添加新属性时,它会在后台逐渐添加一个新列。它不会重新定义整个表。这就是此实现也非常通用的原因。

下面是一个迁移脚本示例,其中一个新属性 Accreditation 添加到限界上下文 Building 中的类 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");
    }
}

关于c# - 如何首先在 EF 代码中处理一个数据库在多个数据库上下文中使用的一个类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39456105/

相关文章:

c# - 如何从服务访问当前登录用户的 HKCU 注册表?

asp.net-mvc-3 - 如何设置新的成员资格和 session 提供程序以在 Windows Azure 中运行?使用 MVC3 和 Web 角色

c# - 拥有多个数据上下文是一种好习惯吗?

python - 精心设计的 Python 应用程序的开源示例

RESTful 最佳实践 - 如何命名报表资源

C# .NET - 固定证书颁发机构 - 我做得对吗?

C# 中的 Javascript 对象

C# 如何将对象列表传递给构造函数?

c# - Entity Framework : Problem associating entities with nullable field

java - 获取Decorator对象的所有类型 : type of wrapped objects and the type of wrapper object