用于存储库中Add和Update方法的特定实体对象

本文关键字:实体 对象 方法 Update 存储 Add 用于 | 更新日期: 2023-09-27 17:53:37

我正试图想出一种方法来设计一个存储库,其中添加和更新仅接受它可以添加/更新的确切数量的数据/属性。

我有以下设计:

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

然而,CreatedUpdated属性并不是我真正想要在数据库外修改的东西。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();
}

现在只有相关的属性出现在每个单独的方法中。

然后我可以创建一个实现所有产品接口的类:

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

所以我的问题是:这是正确的方法吗?

我的想法:

我本可以选择更改Repository上的Add和Update方法,以接受每一位产品数据作为参数,例如Update(int id, string name, decimal price),但是当产品持有的信息量增加时,它将很快失去控制。

我目前的解决方案涉及重复。如果一个产品应该持有Description属性,我必须在不同的接口中指定它。我可以让接口相互实现来解决这个问题…

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?在我看来,这并不理想。

所以我想无论我怎么做都会看到问题。我是不是太挑剔了?我看错了吗?

我应该把事情完全分开,因为它们是不同的事情吗?[实体]/[添加实体参数]/[更新实体参数])?

用于存储库中Add和Update方法的特定实体对象

我认为你是过于复杂的事情,而不是正确地分离你的层。在我看来,前两类是应该如何做的。

据我所知,你的整个问题是你不希望CreatedUpdated属性被错误地修改。然而,您混淆了数据和业务问题。在创建产品时设置产品的创建日期是创建新产品的业务逻辑的一部分,在x和y出现时更新产品的更新日期也是更新产品数据的逻辑过程的一部分。这与验证产品属性是否有效、用户是否获得授权等是同一类型的过程。,所有这些都是业务流程问题,而不是数据存储问题。

您的存储库应该仅仅是数据层的一部分,它只关心如何从数据库中检索请求的产品,如何更新数据库中的产品,或者如何在数据库中创建产品。就是这样。

您需要一个专用的业务层来处理添加或更新产品信息的所有业务逻辑。然后,您将调用该层中的一个方法,其中包含您想要添加的产品的名称和价格,并且在该方法中,您将执行您想要执行的任何验证,确定用户是否被授权进行这些编辑,并设置CreatedDateUpdatedDate(如果有必要)。这个方法将把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,它有许多添加、更新等操作。

那么你为什么不声明base base IProduct接口,它只具有所有操作所需的最小数量的属性,例如描述,类别等。

然后从这个基本接口继承每个动作,例如IProductAdd。产品类本身应该只继承IProduct接口。

然后为每个动作创建新类,例如add,它继承自IProduct add &只需在产品类中添加一些方法,这些方法接受IProductAdd等类型的参数,但使用动作类的实例来执行工作

这就是我如何去做....我会使用反射和属性:

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产品名称:价格:12.30创建日期:29/04/2011 18:41:26更新日期:29/04/2011 18:41:26

Id: 2产品名称:价格:18.50创建:29/04/2011 18:41:28

更新日期:29/04/2011 18:41:28

Id: 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

更新日期:29/04/2011 18:41:29