[英]Specific entity objects for Add and Update methods in a repository
我试图想出一种方法来设计一个存储库,其中添加和更新只接受它可以添加/更新的确切数量的数据/属性。
我有以下设计:
public interface IProduct
{
int Id { get; set; }
string Name { get; set; }
decimal Price { get; set; }
DateTime Created { get; set; }
DateTime Updated { get; set; }
}
public interface IProductRepository
{
void Add(IProduct product);
void Update(IProduct product);
IProduct Get(int id);
IEnumerable<IProduct> GetAll();
}
但是, Created
和Updated
属性并不是我真正想要在数据库之外修改的东西。 添加时Id
不相关,因此我尝试了以下操作:
public interface IProductAdd
{
string Name { get; set; }
decimal Price { get; set; }
}
public interface IProductUpdate
{
int Id { get; set; }
string Name { get; set; }
decimal Price { get; set; }
}
并相应地更新了存储库:
public interface IProductRepository
{
void Add(IProductAdd product);
void Update(IProductUpdate product);
IProduct Get(int id);
IEnumerable<IProduct> GetAll();
}
现在,每个单独的方法中只存在相关的属性。
然后我可以创建一个实现所有产品接口的 class:
public class Product : IProduct, IProductAdd, IProductUpdate
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public DateTime Created { get; set; }
public DateTime Updated { get; set; }
}
所以我的问题是:这是正确的方法吗?
我的想法:
我本可以选择更改存储库上的 Add 和 Update 方法以接受产品数据的每一位作为参数,例如Update(int id, string name, decimal price)
,但是当数量为产品持有的信息会增加。
我目前的解决方案涉及重复。 如果一个产品应该包含一个Description
属性,我将不得不在不同的 3 个接口中指定它。 我可以让接口相互实现来解决这个问题......
public interface IProductAdd
{
string Name { get; set; }
decimal Price { get; set; }
string Description { get; set; }
}
public interface IProductUpdate : IProductAdd
{
int Id { get; set; }
}
public interface IProduct : IProductUpdate
{
DateTime Created { get; set; }
DateTime Updated { get; set; }
}
...但是如果IProductAdd
有一些IProductUpdate
不应该有的东西,我会遇到麻烦。
相关问题:假设我想将产品放在类别中,并且可以直接在每个产品上访问该类别。
public interface ICategory
{
int Id { get; set; }
string Name { get; set; }
string Description { get; set; }
}
public interface IProduct
{
int Id { get; set; }
string Name { get; set; }
decimal Price { get; set; }
DateTime Created { get; set; }
DateTime Updated { get; set; }
ICategory Category { get; set; }
}
当我更改产品时,我想指定类别的 id(因为我正在添加/更新关系,而不是类别本身):
public interface IProductAdd
{
string Name { get; set; }
decimal Price { get; set; }
int CategoryId { get; set; }
}
public interface IProductUpdate
{
int Id { get; set; }
string Name { get; set; }
decimal Price { get; set; }
int CategoryId { get; set; }
}
这导致以下实现:
public class Product : IProduct, IProductAdd, IProductUpdate
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public DateTime Created { get; set; }
public DateTime Updated { get; set; }
public ICategory Category { get; set; }
public int CategoryId { get; set; }
}
看看那个,我使用 Category.Id 还是 CategoryId? 不理想,IMO。
所以我想无论我怎么做,我都会看到问题。 我是不是太挑剔了? 我看错了吗?
我是否应该将事物完全分开,因为它们是不同的事物(例如 [实体] / [添加实体参数] / [更新实体参数])?
我认为您在没有正确分离图层的同时使事情过于复杂。 在我看来,前两节课是应该怎么做的。
据我了解,您的整个问题是您不希望错误地修改Created
和Updated
属性。 但是,您将数据和业务问题混为一谈。 说产品的创建日期应该在产品被创建时设置是创建新产品的业务逻辑的一部分,并且说产品的更新日期应该在 x 和 y 发生时更新也是更新产品的逻辑过程的一部分产品的数据。 这与验证产品属性是否有效、用户是否被授权等类型的过程相同,所有这些都是业务流程问题,而不是数据存储问题。
您的存储库应该只是数据层的一部分,它只关心如何从数据库中检索请求的产品、如何更新数据库中的产品或在数据库中创建产品。 而已。
您需要一个专门的业务层来处理添加或更新产品信息的所有业务逻辑。 然后,您将使用您要添加的产品的名称和价格在该层中调用一个方法,在此方法中您将执行您想要执行的任何验证,确定用户是否有权进行这些编辑,以及如果需要,设置CreatedDate
或UpdatedDate
。 然后,此方法会将Product
实体传递到您的存储库以将其保存在数据库中。
当您想要更改有关如何管理诸如UpdatedDate
之类的事物的逻辑时,以这种方式分离逻辑将变得更加容易(也许您希望某些操作来更改该日期,但不是所有操作)。 如果您尝试在存储库/数据层中处理所有这些,当您摆脱琐碎的用例时,它很快就会变得不堪重负和令人困惑。
还有一点。 IProduct
是一个业务实体,这意味着您根本不必将它暴露给您的表示层。 因此,如果您不想冒险让开发人员接触某些属性,您可以使用 MVC 架构通常称为 ViewModels 的东西。 本质上,这些是在表示层上使用的数据结构,然后业务层可以将这些 ViewModel 转换为实际的业务实体。
因此,例如,您可以:
public class ProductViewModel
{
int Id { get; set; }
string Name { get; set; }
decimal Price { get; set; }
int CategoryId { get; set; }
}
您的表示层会将填写好的ProductViewModel
传递给业务层的AddProduct()
或UpdateProduct()
方法,然后将检索指定的数据库的IProduct
实体并使用ProductViewModel
确定如何更新(或创建新)数据库实体。 这样,您永远不会公开两个DateTime
属性,但仍然可以完全控制它们的设置方式和时间。
如果我在这里误解了你,请原谅我,但对我来说,你的设计逻辑似乎是不正确的。 本质上,您的基本实体是 Product,它具有许多操作 Add、Update 等。
那么为什么不声明基本 IProduct 接口,它只具有所有操作所需的最少属性,例如描述、类别等。
然后只需获取每个操作,例如 IProductAdd 从这个基本接口继承。 产品 class 本身应该只继承自 IProduct 接口。
然后为每个操作创建新类,例如 add 继承自 IProduct add 并在产品 class 中添加一些方法,这些方法接受 IProductAdd 等类型的参数,但使用操作类的实例来执行工作
这就是我对 go 的看法......我会使用反射和属性:
namespace StackOverFlowSpike.Attributes
{
[AttributeUsage(AttributeTargets.Property)]
public class ReadOnlyAttribute : Attribute
{
public ReadOnlyAttribute() { }
}
}
using StackOverFlowSpike.Attributes;
namespace StackOverFlowSpike.Entities
{
public interface IEntity
{
[ReadOnly]
public int Id { get; set; }
}
}
using System;
using StackOverFlowSpike.Attributes;
namespace StackOverFlowSpike.Entities
{
public class Product : IEntity
{
[ReadOnly]
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
[ReadOnly]
public DateTime Created { get; set; }
[ReadOnly]
public DateTime Updated { get; set; }
}
}
using StackOverFlowSpike.Entities;
using System.Collections.Generic;
namespace StackOverFlowSpike.Repositories
{
public interface IRepository<T> where T : IEntity
{
void Add(T item);
void Update(T item);
T Get(int id);
IEnumerable<T> GetAll();
}
}
using System;
using System.Linq;
using System.Threading;
using System.Reflection;
using System.Collections.Generic;
using StackOverFlowSpike.Entities;
using StackOverFlowSpike.Attributes;
namespace StackOverFlowSpike.Repositories
{
public class ProductRepositoryMock : IRepository<Product>
{
#region Fields and constructor
private IList<Product> _productsStore;
public ProductRepositoryMock()
{
_productsStore = new List<Product>();
}
#endregion
#region private methods
private int GetNewId()
{
return _productsStore
.OrderByDescending(p => p.Id)
.Select(p => p.Id).FirstOrDefault() + 1;
}
private void PopulateProduct(Product storedProduct, Product incomingProduct)
{
foreach (var p in storedProduct.GetType().GetProperties())
{
// check if it is NOT decorated with ReadOnly attribute
if (!(p.GetCustomAttributes(typeof(ReadOnlyAttribute), false).Length > 0))
{
// i will use reflection to set the value
p.SetValue(storedProduct, p.GetValue(incomingProduct, null), null);
}
}
}
private void Synchronise(Product storedProduct, Product incomingProduct)
{
foreach (var p in storedProduct.GetType().GetProperties())
p.SetValue(incomingProduct, p.GetValue(storedProduct, null), null);
}
#endregion
public void Add(Product product)
{
Product newProduct = new Product();
newProduct.Id = GetNewId();
newProduct.Created = DateTime.Now;
newProduct.Updated = DateTime.Now;
PopulateProduct(newProduct, product);
_productsStore.Add(newProduct);
Synchronise(newProduct, product);
// system takes a quick nap so we can it really is updating created and updated date/times
Thread.Sleep(1000);
}
public void Update(Product product)
{
var storedProduct = _productsStore.Where(p => p.Id == product.Id).FirstOrDefault();
if (storedProduct != null)
{
PopulateProduct(storedProduct, product);
storedProduct.Updated = DateTime.Now;
// system takes a quick nap so we can it really is updating created and updated date/times
Synchronise(storedProduct, product);
Thread.Sleep(1000);
}
}
public Product Get(int id)
{
Product storedProduct = _productsStore.Where(p => p.Id == id).FirstOrDefault();
Product resultProduct = new Product()
{
Id = storedProduct.Id,
Name = storedProduct.Name,
Price = storedProduct.Price,
Created = storedProduct.Created,
Updated = storedProduct.Updated
};
return resultProduct;
}
public IEnumerable<Product> GetAll()
{
return _productsStore;
}
}
}
这是一个小控制台程序来测试
using System;
using System.Text;
using System.Collections.Generic;
using StackOverFlowSpike.Entities;
using StackOverFlowSpike.Repositories;
namespace StackOverFlowSpike
{
class Program
{
static void Main(string[] args)
{
Product p1 = new Product()
{
Created = Convert.ToDateTime("01/01/2012"), // ReadOnly - so should not be updated with this value
Updated = Convert.ToDateTime("01/02/2012"), // ReadOnly - so should not be updated with this value
Id = 99, // ReadOnly - should not be udpated with this value
Name = "Product 1",
Price = 12.30m
};
Product p2 = new Product()
{
Name = "Product 2",
Price = 18.50m,
};
IRepository<Product> repo = new ProductRepositoryMock();
// test the add
repo.Add(p1);
repo.Add(p2);
PrintProducts(repo.GetAll());
// p1 should not change because of change in Id
p1.Id = 5; // no update should happen
p1.Name = "Product 1 updated";
p1.Price = 10.50m;
// p2 should update name and price but not date created
p2.Name = "Product 2 updated";
p2.Price = 17m;
p2.Created = DateTime.Now;
repo.Update(p1);
repo.Update(p2);
PrintProducts(repo.GetAll());
Console.ReadKey();
}
private static void PrintProducts(IEnumerable<Product> products)
{
foreach (var p in products)
{
Console.WriteLine("Id: {0}\nName: {1}\nPrice: {2}\nCreated: {3}\nUpdated: {4}\n",
p.Id, p.Name, p.Price, p.Created, p.Updated);
}
Console.WriteLine(new StringBuilder().Append('-', 50).AppendLine().ToString());
}
}
}
试验结果:
ID:1 名称:产品 1 价格:12.30 创建时间:29/04/2011 18:41:26 更新时间:29/04/2011 18:41:26
ID:2 名称:产品 2 价格:18.50 创建时间:29/04/2011 18:41:28
ID:1 名称:产品 1 价格:12.30 创建时间:29/04/2011 18:41:26 更新时间:29/04/2011 18:41:26
ID:2 名称:产品 2 更新价格:17 创建时间:29/04/2011 18:41:28
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.