在 DDD 中将 MongoDB 与 Entity Framework Core 结合使用:为实体 Id 生成 ObjectId,同时保持抽象

问题描述 投票:0回答:2

我正在使用 MongoDB 和 Entity Framework Core 在我的应用程序中实现域驱动设计 (DDD) 架构。我的目标是保持领域逻辑和数据库基础设施之间的明确分离。为了实现这一目标,我使用域和基础设施层的单独项目构建了我的解决方案。

在我的领域项目中,我定义了一个

Customer
实体和
CustomerService
。 Customer 实体有一个字符串属性 Id:

public class Customer
{        
    public string Id { get; set; } 
}

public class MyDbContext : DbContext
{
  public MyDbContext(DbContextOptions options) : base(options)
  {
  }

  public DbSet<Customer> Customers { get; set; }      
}

public class CustomerService
{
   private readonly MyDbContext _mydbContext;
   
   public CustomerService(MyDbContext mydbContext)
   {
      _mydbContext = mydbContext
   }   
   
   public Customer Get(string id)
   {
      return _mydbContext.Customers.Find(id);
   }    
   
   public Customer Create(Customer customer)
   {
      _mydbContext.Customers.Add(customer);
      _mydbContext.SaveChanges();
   } 
}

在基础设施项目中,我使用

MongoDB.EntityFrameworkCore
包扩展了 MyDbContext 和 MongoDbContext:

public class MongoDbContext : MyDbContext
{
    public static MongoDbContext Create(IMongoDatabase database) =>
        new(new DbContextOptionsBuilder<MongoDbContext>()
            .UseMongoDB(database.Client, database.DatabaseNamespace.DatabaseName)
            .Options);

    public MongoDbContext(DbContextOptions options) : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Customer>(c =>
        {
            c.ToCollection("customers");
            c.HasKey(c => c.Id);
            c.Property(p => p.Id)
                .HasConversion(id =>ObjectId.Parse(id),oid => oid.ToString());
        });        
    }    
}

需要注意的是,Customer 实体中的

Id
属性当前定义为
string
。虽然我可以将其重新定义为类型
MongoDB.Bson.ObjectId
,但这样做会引入对域层中
MongoDB.Bson
包的依赖,这与我想要的抽象相矛盾。目标是让领域层不知道底层数据库的实现。
此外,请注意,我正在使用预先存在的 MongoDB 文档,其中
Id
字段的类型为
ObjectId
。否则,我可能会选择 Guid 或其他原始数据类型。

获取客户可以无缝进行,因为

HasConversion()
方法处理从 ObjectId 到字符串的转换。但是,在创建新客户时,需要在将实体添加到 DbContext 期间填充 Id 属性。我的偏好是利用 MongoDB 生成的 ObjectId 来实现此目的。

如何应对这一挑战,同时保留域和基础设施层之间的抽象?

mongodb mongodb-.net-driver mongodb.entityframeworkcore
2个回答
0
投票

我通过实现自定义值生成器让它工作

internal class ObjectIdValueGenerator : ValueGenerator<string>
 {
     public override bool GeneratesTemporaryValues => false;

     public override string Next(EntityEntry entry)
     {
         return ObjectId.GenerateNewId().ToString();
     }
 }

然后在OnModelCreating()方法中注册它

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.Entity<Customer>(c =>
    {
        c.ToCollection("customers");
        c.HasKey(c => c.Id);
        c.Property(p => p.Id)
            .HasConversion(id =>ObjectId.Parse(id),oid => oid.ToString())
            .HasValueGenerator<ObjectIdValueGenerator>();
    });        
}    

0
投票

为了解决使域层与 MongoDB 实现细节无关的挑战,同时仍然在域驱动设计 (DDD) 架构中利用 MongoDB 的 ObjectId 作为主键,您可以实施一些策略。这是一种全面的方法,可以保持关注点分离,并允许在基础设施中使用 MongoDB 生成的 ObjectId,同时保持域不知道它:

实现这一目标的步骤: 域层:

在域层中使用字符串 Id 属性定义 Customer 实体。

public class Customer
{        
    public string Id { get; set; } 
    // Other properties...
}
Infrastructure Layer:

Extend the MyDbContext to MongoDbContext and handle the conversion between ObjectId and string.


public class MongoDbContext : MyDbContext
{
    public static MongoDbContext Create(IMongoDatabase database) =>
        new(new DbContextOptionsBuilder<MongoDbContext>()
            .UseMongoDB(database.Client, database.DatabaseNamespace.DatabaseName)
            .Options);

    public MongoDbContext(DbContextOptions options) : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Customer>(c =>
        {
            c.ToCollection("customers");
            c.HasKey(c => c.Id);
            c.Property(p => p.Id)
                .HasConversion(
                    id => ObjectId.Parse(id), 
                    oid => oid.ToString()
                );
        });
    }
}

服务层:

修改 CustomerService 以处理创建过程,包括分配 MongoDB 的 ObjectId。

public class CustomerService
{
    private readonly MyDbContext _mydbContext;

    public CustomerService(MyDbContext mydbContext)
    {
        _mydbContext = mydbContext;
    }

    // Method to retrieve a customer
    public Customer Get(string id)
    {
        return _mydbContext.Customers.Find(id);
    }

    // Method to create a customer
    public Customer Create(Customer customer)
    {
        // Generate a new ObjectId and assign it to the customer's Id property
        customer.Id = ObjectId.GenerateNewId().ToString();

        // Add and save changes
        _mydbContext.Customers.Add(customer);
        _mydbContext.SaveChanges();

        return customer;
    }
}

要点:

问题分离:通过将 Customer.Id 作为域层中的字符串保留,该域不受任何特定于 MongoDB 的依赖关系的影响。

ObjectId 生成:在 CustomerService.Create 方法中,使用 ObjectId.GenerateNewId() 生成新的 ObjectId,并在分配给客户的 Id 之前将其转换为字符串。这意味着域只看到一个字符串,但由于 MongoDbContext 中配置的转换,MongoDB 会将其理解为 ObjectId。

转换处理:MongoDbContext.OnModelCreating 中的转换 我的朋友,您需要确保 Id 字段在创建、读取、更新和删除操作期间由 MongoDB 正确管理,而不会将 ObjectId 暴露给域层。

我相信,通过遵循这种方法,您可以有效地维护干净的 DDD 架构,同时为您的实体利用 MongoDB 的 ObjectId,保持域逻辑干净并与基础设施问题解耦。

不要惊慌,像水一样,希望我能帮到你!

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