chuwik
chuwik

Reputation: 867

Soft delete repository while keeping property private

I have a set of Entity Framework (EF) based repositories, some of which deal with entities that can be soft deleted (not all of them can). The entities are auto generated by EF. So far I have:

This all works fine. However, this code is part of an internal library distributed to users who call the repositories directly, and I don't want them to alter the "IsDeleted" property, only read it or delete entities calling the method. Right now they could do that because the setter is public.

How can I change my code design in order to do this? I can't change ICanBeSoftDeleted and remove the setter, because then I wouldn't be able to modify it from the SoftDeleteRepositories.

Thanks

Update: for the moment I've solved the problem by removing the "set" from the interface and setting the value in the repository with reflection:

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

    Update(entity);
}

However this feels like a patch to me, I don't think it's solving the bigger design problem...

Upvotes: 0

Views: 1435

Answers (4)

Keith Payne
Keith Payne

Reputation: 3082

You will have to keep the EF classes behind the wall and map to POCO's; or type them as an interface (that does not declare the setter) when handing them off to consumers.

The second option leaves the objects open to have the deleted flag set by reflection.

EDIT: Further analysis of the code that you posted leads to the following question:

Do you intend to give consumers of your API the ability to declare Repositories ?

It would seem wiser to expose only non-generic repositories - e.g. CustomerRepository, UserRepository, PurchaseRepository in your API.

The non-generic API then forms a clean boundary from which you can separate your EF classes from the POCOs for the API consumer.

Upvotes: 1

Colin
Colin

Reputation: 22595

Could you do it by checking whether the entity is an implementation of ICanBeSoftDeleted inside your RepositoryBase instead?

Using an extension from here: 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; }
}

Upvotes: 1

Colin
Colin

Reputation: 22595

How about this:

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

Then the models can inherit from the abstract class instead

Upvotes: 0

Lajos Arpad
Lajos Arpad

Reputation: 76434

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

EDIT: Here I describe the creation of a property which can be used to detect whether a property or functionality is called from a valid place.

  1. You need a protected variable in your class where you want to validate the IsDeleted property. For simplicity, let's suppose it is a string and let it be called softDeleteKey.

  2. You need a public setter for that variable, let's call it setSoftDeleteKey for now.

  3. You need a private function which checks the validity of your key, returns true if it is valid and false if it is not valid. Let's call this validateSoftDeleteKey.

  4. Implement a read-only property called isSoftDeleteKeyValid which will call the function described in 3.

  5. In your IsDeleted property check isSoftDeleteKeyValid. If it returns false, throw an exception. If IsDeleted was successful, set softDeleteKey to an invalid value.

  6. In your SoftDelete method call setSoftDeleteKey with a valid value before you set the IsDeleted property. If an exception occurs, then call setSoftDeleteKey with an invalid value.

I hope these ideas help you.

Upvotes: 0

Related Questions