繁体   English   中英

将实体保存到数据库的设计模式

[英]Design pattern for saving entities into database

我有一个类似于下面的类(C#):

public class Product {

    public int ID {get;set;}
    public string Name {get;set;}
    public double Price {get;set;}

    public void Save() {
        string sql = "INSERT INTO Product.....";
        Database.Execute(sql);
    }

    public void Delete() {
        string sql = "DELETE Product WHERE.....";
        Database.Execute(sql);
    }
}

我主要担心的是上面的代码违反了SOLID原则,因为它负责创建和删除自身。

也许这些Save和Delete方法应该放在Product实体之外的某个地方(Factory / Repository可能?)。

我相信Facade模式在你的情况下会做得很好。 Facade模式也称为服务层。

在你的情况下,你基本上会有一个服务(一个类),它将拥有你需要的所有方法。 您的服务应该是这样的。

class ProductService 
{
    public void Save(Product product)
    {
       // SAVE THE PRODUCT
    }

    public void Delete(Product product)
    {
        // DELETE PRODUCT
    }
}

您希望将类注入要保存或删除产品的位置。 这样,您所要做的所有工作都将在一个单独的类中,您的代码将变得更加清晰。 让所有这些在存储过程中插入和删除statemenet也是一个好主意。

我将介绍您的模型实体,命令和查询模式以及数据库层或存储库。

您的模型是您的Product ,此对象应该是一个普通对象:

public class Product : IEntity {
    public int ID { get; set; }
    public string Name { get; set; }
    public double Price { get; set; }
}

接下来,我将创建一个用于处理此实体的命令和查询接口:

public interface ICommand {} // Marker interface

public interface IQuery<TResult> {} // Marker interface

接下来为ICommandIQuery定义处理程序:

public interface IHandleQuery<TQuery, TResult> where TQuery : IQuery<TResult> 
{
    TResult Handle(TQuery query);
}

public interface IHandleCommand<TCommand> where TCommand : ICommand
{
    void Handle(TCommand command);
}

现在,您可以清楚地指出并分离您的写入(命令)和读取(查询)方面。

这意味着我们可以创建一个命令及其处理程序来保存您的Product如:

public class SaveProduct : ICommand 
{
    public string Name { get; private set; }
    public double Price { get; private set; }

    public SaveProduct(string name, double price) 
    {
        Name = name;
        Price = price;
    }
}

public class HandleSaveProduct : IHandleCommand<SaveProduct> 
{
    private readonly IRepository<Product> _productRepository;

    public HandleSaveProduct(IRepository<Product> productRepository) 
    {
        _productRepository = productRepository;
    }

    public void Handle(SaveProduct command) 
    {
        var product = new Product {
            Name = command.Name,
            Price = command.Price
        };

        _productRepository.Save(product);
    }
}

在上面我们已经定义了一个用于处理这个实体的存储库,但是你可以在这里直接依赖你的数据库上下文并对它执行查询/命令,或者你可以使用GenericRepository<TEntity> : IRepository<TEntity>来实现存储库模式。只是单独的产品库:

public interface IEntity { } // Marker interface

public interface IRepository<TEntity> where TEntity : IEntity 
{
    TEntity Get(object primaryKey);

    void Save(TEntity entity); // should handle both new and updating entities

    void Delete(TEntity entity);

}

public class ProductRepository : IRepository<Product> 
{
    public Product Get(object primaryKey) 
    {
        // Database method for getting Product
    }

    public void Save(Product entity) 
    {
        // Database method for saving Product
    }

    public void Delete(Product entity) 
    {
        // Database method for deleting Product
    }
}

您永远不应该将您的Product实体返回到您的UI,而是使用视图模型,例如:

public class ProductViewModel {
    public int ID { get; set; }
    public string Name { get; set; }
    public double Price { get; set; }
    public DateTime Whatever { get; set; }
}

public class GetProductById : IQuery<ProductViewModel>
{
    public int Id { get; private set; }

    public GetProductById(int id)
    {
        Id = id;
    }
}

public class HandleGetProductById : IHandleQuery<GetProductById, ProductViewModel>
{
    private readonly IRepository<Product> _productRepository;

    public HandleGetProductById(IRepository<Product> productRepository) 
    {
        _productRepository = productRepository;
    }

    public ProductViewModel Handle(GetProductById query)
    {
        var product = _productRepository.Get(query.Id);
        return product.Select(x => new ProductViewModel {
            Name = x.Name,
            Price = x.Price;
        });
    }
}

请注意这是用记事本写的,可能无法100%编译,但你应该知道如何分离各种组件以便遵循SOLID。 :-)

你似乎想要某种类似Repository的存储库 你已经在[问题]中提到了它。 该链接仅供参考 - 我建议您实施。 为什么?

因为像@Igor说的那样,如果你使用的是ORM,那么你将免费获得这份合同。 例如,NHibernate有一个带有Query<T>()Save()Delete()等方法的ISession 这就是你所需要的。

我使用的几乎所有项目都使用了这种“基础设施”ORM合同的抽象(服务/存储库/等),所谓的抽象很弱,只能创造更多的代码来维持和更高的技术债务和错误。

采取务实的方法:

  • 不要通过内部的ADO.NET调用创建自己的Repository / ORM抽象来重新发明轮子。 使用像Fluent NHibernate这样的实体ORM,这使得映射变得简单并且易于与数据交互(其他完美的替代方案可能是实体框架等)。 如果这对你来说太多了,试试像Dapper这样非常简单的东西 - 它是一个非常轻量级的ORM,可以映射到你的模型,就像怪异的魔法一样,你仍然可以编写所有自己的SQL。 您将获得使用的ORM合约界面,我相信这是您在这里所要求的,并且您可以继续构建您的应用程序,而不是考虑过度工程。
  • 通过在控制器中使用ORM合同来保持简单('控制器'不必是MVC控制器,它可以是应用程序的UI入口点。记住:避免不必要的抽象)。 这是一些简单的例子。
  • 人们希望保持干燥 ,但开发人员必须使用#reusingallthethings的奇怪成瘾意味着他们经常拥有存储库或服务,它们包含完美的ORM合同电话,并且通常只有一两次使用。 忘了重用! 使用规则三 ,首先查询,保存和删除控制器中的逻辑,并在您知道需要时仅提取可重用的代码。
    我知道这些例子是微不足道的 ,但只是想象你需要返回一些数据,查询需要一些长linq表达式或带有连接的复杂选择。 现在想象你在几个地方需要这个相同的查询(它发生。不经常,但你会有一些) - 复制并粘贴它! 恩,那就对了; 你不敢相信我说过,但我做到了。 将相同的10行复制并粘贴到代码中的2,3或4个位置。 这完全没问题。 没有人会死。 只要linq表达式本身( GetTop15TransactionsWithoutFeesExcludingCreditsGroupByDayRecentAtTop()任何人?),您不需要查询对象或Repository方法。

HTH。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM