【问题标题】:Soft delete repository while keeping property private软删除存储库,同时保持属性私有
【发布时间】:2013-07-10 16:35:23
【问题描述】:

我有一组基于实体框架 (EF) 的存储库,其中一些处理可以软删除的实体(并非所有实体都可以)。实体由 EF 自动生成。到目前为止,我有:

  • 可软删除的实体实现了ICanBeSoftDeleted接口:

    public interface ICanBeSoftDeleted
    {
        bool IsDeleted { get; set; }
    }
    
  • 对于这些实体,我使用实现 ISoftDeleteRepository 的存储库:

    public interface ISoftDeleteRepository<T> : IRepository<T> where T : class, ICanBeSoftDeleted    
    {
        void SoftDelete(T entity);   
        void SoftDelete(Expression<Func<T, bool>> where);
    }
    
  • 我还有一个基类 SoftDeleteRepositoryBase,它扩展了 RepositoryBase 并添加了软删除方法:

    public abstract class SoftDeleteRepositoryBase<T> : RepositoryBase<T> where T : class, ICanBeSoftDeleted
    {
        public virtual void SoftDelete(T entity)
        {
            entity.IsDeleted = true;
            Update(entity);
        }
    
        public virtual void SoftDelete(Expression<Func<T, bool>> where)
        {
            var entitiesToDelete = GetMany(where).AsEnumerable();
            foreach (T entity in entitiesToDelete)
            {
                this.Delete(entity);
            }
        }
    }
    

这一切都很好。但是,此代码是分发给直接调用存储库的用户的内部库的一部分,我不希望他们更改“IsDeleted”属性,只读取它或删除调用该方法的实体。现在他们可以这样做,因为 setter 是公开的。

如何更改我的代码设计才能做到这一点?我无法更改 ICanBeSoftDeleted 并删除设置器,因为那样我将无法从 SoftDeleteRepositories 修改它。

谢谢

更新:目前我已经通过从界面中删除“set”并使用反射设置存储库中的值来解决问题:

public virtual void Delete(T entity)
{
    PropertyInfo propertyInfo = entity.GetType().GetProperty("IsDeleted");
    propertyInfo.SetValue(entity, true);

    Update(entity);
}

然而这对我来说就像一个补丁,我不认为它解决了更大的设计问题......

【问题讨论】:

    标签: c# entity-framework oop repository


    【解决方案1】:

    您可以通过检查实体是否是您的 RepositoryBase 中的 ICanBeSoftDeleted 的实现来代替吗?

    使用此处的扩展程序: http://bradhe.wordpress.com/2010/07/27/how-to-tell-if-a-type-implements-an-interface-in-net/

    public static class TypeExtensions
    {
        //http://bradhe.wordpress.com/2010/07/27/how-to-tell-if-a-type-implements-an-interface-in-net/
        public static bool IsImplementationOf(this Type baseType, Type interfaceType)
        {
            return baseType.GetInterfaces().Any(interfaceType.Equals);
        }
    }
    
    public interface IRepository<T> 
    {
        void Delete(T entity);
    }
    
    public class RepositoryBase<T> : IRepository<T> where T : class
    {
        public void Delete(T entity)
        {
            if (typeof(T).IsImplementationOf(typeof(ICanBeSoftDeleted)))
            {
                ((ICanBeSoftDeleted)entity).IsDeleted = true;
                //etc
            }
            else
            {
                //hard delete
            }
        }
    }
    
    public class Customer : ICanBeSoftDeleted
    {
        public bool IsDeleted { get; set; }
    }
    
    public class UOW
    {
    
        private IRepository<T> GetRepository<T>()
        {
            return (IRepository<T>)new RepositoryBase<T>();
        }
    
        public IRepository<Customer> CustomerRepository
        {
            get
            {
                return GetRepository<Customer>();
            }
        }
    }
    
    public interface ICanBeSoftDeleted
    {
        bool IsDeleted { get; set; }
    }
    

    【讨论】:

    • 可以软删除的实体也可以硬删除。硬删除方式是在RepositoryBase上,软删除方式是只在SoftRepositoryBase上增加,所以只有那些实体才能使用。所以很遗憾不能使用这种方法。
    • 因此用户可以调用 SoftDelete() 将 IsDeleted 设置为 true(并调用 Update),但您不想让他们直接将 IsDeleted 设置为 true。那是因为你担心他们可能会忘记调用 Update() 吗?
    • 不,这是因为软删除不只是这样;我简化了示例的方法,但我也想始终记录活动,也许在删除实体时做其他事情等等......基本上我想控制它周围的业务逻辑。
    • 好吧,如果你想避免 DTO 和业务逻辑层,那么我想你可以使用 public bool IsDeleted {get;内部集;}。如果 Repositories 和 Entities 不在同一个命名空间中,您可以使用 InternalsVisibleTo
    • 您不能在界面中设置“内部集”。作为旁注,“InternalsVisibleTo”程序集属性是为了让另一个程序集可以看到标记的程序集中的内部,它与命名空间无关。
    【解决方案2】:

    您必须将 EF 类保留在墙后并映射到 POCO;或者在将它们交给消费者时将它们键入为接口(不声明设置器)。

    第二个选项使对象保持打开状态,以通过反射设置已删除标志。

    编辑:对您发布的代码的进一步分析导致以下问题:

    您是否打算让 API 的使用者能够声明存储库?

    只公开非通用存储库似乎更明智 - 例如API 中的 CustomerRepository、UserRepository、PurchaseRepository。

    然后,非泛型 API 形成一个清晰的边界,您可以将 EF 类与 API 使用者的 POCO 分开。

    【讨论】:

    • 这个想法是只公开非通用存储库。问题是,如果 ICustomerRepository 是公共的,那么它扩展的抽象类 RepositoryBase 也需要是公共的。这将使人们能够创建自己的存储库来扩展 RepositoryBase 自己,这是我不想要的……有什么想法吗?
    • 我想使用 EF 生成的 POCO 作为 DTO,所以我不会选择第一个选项。我想到了第二个,从 ICanBeSoftDeleted 接口中删除“设置”,并在存储库中使用反射设置它。然而,这对我来说似乎有点“hacky”......会有任何性能问题吗?
    • 这不会阻止消费者使用与您相同的技术来摆弄已删除的标志。
    【解决方案3】:
    public bool IsDeleted
    {
        get
        {
            return this.isDeleted;
        }
        //you can use protected if you intend to inherit the property and override it
        private set
        {
            this.isDeleted= value;
        }
    }
    

    编辑: 这里我描述了一个属性的创建,它可以用来检测一个属性或功能是否从一个有效的地方被调用。

    1. 您的类中需要一个受保护的变量来验证 IsDeleted 属性。为简单起见,我们假设它是一个字符串,并将其命名为 softDeleteKey。

    2. 您需要一个用于该变量的公共设置器,我们暂时称其为 setSoftDeleteKey。

    3. 您需要一个私有函数来检查您的密钥的有效性,如果有效则返回 true,如果无效则返回 false。我们称之为 validateSoftDeleteKey。

    4. 实现一个名为 isSoftDeleteKeyValid 的只读属性,它将调用 3 中描述的函数。

    5. 在您的 IsDeleted 属性中检查 isSoftDeleteKeyValid。如果返回 false,则抛出异常。如果 IsDeleted 成功,请将 softDeleteKey 设置为无效值。

    6. 在设置 IsDeleted 属性之前,在使用有效值的 SoftDelete 方法中调用 setSoftDeleteKey。如果发生异常,则使用无效值调用 setSoftDeleteKey。

    我希望这些想法对你有所帮助。

    【讨论】:

    • 将 setter 设为私有或受保护会阻止我在存储库的 SoftDelete() 方法中修改其值。
    • 然后您可以创建一个 bool 属性,当且仅当设置 IsDeleted 是有效操作时,该属性才为真。如果从无效位置调用 setter,则在您的 IsDeleted 属性中忘记受保护的关键字并在 setter 中抛出和异常。实现一个 InvalidReferenceToIsDeleteSetterException 并抛出它......
    • 我该怎么做?该方法将由引用它的外部工具从程序集内部和外部调用。你能展示一些示例代码吗?
    • @chuwik,我已经用上一条消息中提到的 bool 属性的描述编辑了我的答案。
    • 感谢编辑!但是,在这种情况下,反射对我来说仍然是一种更清洁的解决方案。
    【解决方案4】:

    这个怎么样:

    public interface ICanBeSoftDeleted
    {
        bool IsDeleted { get; }
    }
    
    public abstract class CanBeSoftDeleted : ICanBeSoftDeleted
    {
        private bool _IsDeleted;
        public bool IsDeleted
        {
            get { return _IsDeleted; }
        }
    
        internal void SetIsDeleted(bool value) { _IsDeleted = value; }
    }
    

    然后模型可以从抽象类继承

    【讨论】:

      猜你喜欢
      • 2020-02-05
      • 2014-12-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-11
      • 1970-01-01
      • 2021-07-25
      • 1970-01-01
      相关资源
      最近更新 更多