将实体保存到数据库的设计模式
Design pattern for saving entities into database
我有一个类似于下面的 class (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),其中将包含您需要的所有方法。您的服务应如下所示。
class ProductService
{
public void Save(Product product)
{
// SAVE THE PRODUCT
}
public void Delete(Product product)
{
// DELETE PRODUCT
}
}
您想将 class 插入到您想要保存或删除产品的位置。这样,您必须做的所有工作都将放在一个单独的 class 中,并且您的代码将变得更加清晰。
在存储过程中包含所有这些插入和删除语句也是一个好主意。
我将介绍您的模型实体、命令和查询模式以及数据库层或存储库。
你的模型就是你的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
接下来定义 ICommand
和 IQuery
的处理程序:
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);
}
}
In the above we have defined an repository for handling this entity,
you can however depend directly on your database context here and do
the queries/commands to it or you can implement the repository pattern
using an GenericRepository<TEntity> : IRepository<TEntity>
or just
the separate product repository:
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
}
}
你永远不应该 return 你的产品实体到你的 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 之类的合同。你已经在你的 [kind of] 问题中提到了它。 link 仅供参考 - 我 不 建议您实施一个。为什么?
因为就像@Igor 所说,如果您使用的是 ORM,那么您将免费获得这份合同。例如,NHibernate 有一个 ISession
和 Query<T>()
、Save()
、Delete()
等方法。这就是您所需要的。
我从事的几乎每个项目都使用了对这个 "infrastructure" ORM 契约的一些抽象(service/repository/etc),所述抽象很弱,只能用于创建更多代码来维护和技术债务和错误的风险更高。
采取务实的方法:
- 不要通过在内部调用 ADO.NET 创建自己的 Repository/ORM 抽象来重新发明轮子。使用像 Fluent NHibernate which makes mapping simple and interacting with the data easy (other perfectly sound alternatives might be Entity Framework, etc). If that's too much party for you, try something really simple like Dapper 这样的可靠 ORM - 它是一个非常轻量级的 ORM,可以像变魔术一样映射到您的模型,您仍然可以编写自己的 SQL。您将使用您的 ORM 合同接口,我相信这就是您在这里要求的,您可以继续构建您的应用程序,而不是考虑过度工程。
- 通过在控制器中使用 ORM 契约来保持简单('controllers' 不必是 MVC 控制器,它可以是应用程序的任何 UI 入口点。记住:避免不必要的抽象)。 Here are some simple examples.
- 人们希望 keep things DRY, but the bizarre addiction developers have to #reusingallthethings means they often have Repositories or Services which wrap a perfectly fine ORM contract call and often only have one or two usages. Forget about reuse! Use the Rule of Three 并从进行查询开始,在控制器中保存和删除逻辑,并且仅在知道需要时才提取可重用代码。
我知道 这些例子是
琐碎,
但想象一下你需要 return 一些数据和查询
需要一些长的 linq 表达式或复杂的 select 连接。现在假设您在几个地方需要相同的查询(它发生了。不经常,但您会有一些)- 复制并粘贴它! 是的,没错;你不能相信我说过但我做到了。将相同的 10 行复制并粘贴到代码中的 2、3 或 4 个位置。完全没问题。没有人会死。只要 linq 表达式本身(GetTop15TransactionsWithoutFeesExcludingCreditsGroupByDayRecentAtTop()
任何人?),您就不需要查询对象或存储库方法。
HTH.
我有一个类似于下面的 class (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),其中将包含您需要的所有方法。您的服务应如下所示。
class ProductService
{
public void Save(Product product)
{
// SAVE THE PRODUCT
}
public void Delete(Product product)
{
// DELETE PRODUCT
}
}
您想将 class 插入到您想要保存或删除产品的位置。这样,您必须做的所有工作都将放在一个单独的 class 中,并且您的代码将变得更加清晰。 在存储过程中包含所有这些插入和删除语句也是一个好主意。
我将介绍您的模型实体、命令和查询模式以及数据库层或存储库。
你的模型就是你的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
接下来定义 ICommand
和 IQuery
的处理程序:
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);
}
}
In the above we have defined an repository for handling this entity, you can however depend directly on your database context here and do the queries/commands to it or you can implement the repository pattern using an
GenericRepository<TEntity> : IRepository<TEntity>
or just the separate product repository:
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
}
}
你永远不应该 return 你的产品实体到你的 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 之类的合同。你已经在你的 [kind of] 问题中提到了它。 link 仅供参考 - 我 不 建议您实施一个。为什么?
因为就像@Igor 所说,如果您使用的是 ORM,那么您将免费获得这份合同。例如,NHibernate 有一个 ISession
和 Query<T>()
、Save()
、Delete()
等方法。这就是您所需要的。
我从事的几乎每个项目都使用了对这个 "infrastructure" ORM 契约的一些抽象(service/repository/etc),所述抽象很弱,只能用于创建更多代码来维护和技术债务和错误的风险更高。
采取务实的方法:
- 不要通过在内部调用 ADO.NET 创建自己的 Repository/ORM 抽象来重新发明轮子。使用像 Fluent NHibernate which makes mapping simple and interacting with the data easy (other perfectly sound alternatives might be Entity Framework, etc). If that's too much party for you, try something really simple like Dapper 这样的可靠 ORM - 它是一个非常轻量级的 ORM,可以像变魔术一样映射到您的模型,您仍然可以编写自己的 SQL。您将使用您的 ORM 合同接口,我相信这就是您在这里要求的,您可以继续构建您的应用程序,而不是考虑过度工程。
- 通过在控制器中使用 ORM 契约来保持简单('controllers' 不必是 MVC 控制器,它可以是应用程序的任何 UI 入口点。记住:避免不必要的抽象)。 Here are some simple examples.
- 人们希望 keep things DRY, but the bizarre addiction developers have to #reusingallthethings means they often have Repositories or Services which wrap a perfectly fine ORM contract call and often only have one or two usages. Forget about reuse! Use the Rule of Three 并从进行查询开始,在控制器中保存和删除逻辑,并且仅在知道需要时才提取可重用代码。
我知道 这些例子是 琐碎, 但想象一下你需要 return 一些数据和查询 需要一些长的 linq 表达式或复杂的 select 连接。现在假设您在几个地方需要相同的查询(它发生了。不经常,但您会有一些)- 复制并粘贴它! 是的,没错;你不能相信我说过但我做到了。将相同的 10 行复制并粘贴到代码中的 2、3 或 4 个位置。完全没问题。没有人会死。只要 linq 表达式本身(GetTop15TransactionsWithoutFeesExcludingCreditsGroupByDayRecentAtTop()
任何人?),您就不需要查询对象或存储库方法。
HTH.