In part 1 we covered the web.config changes and the classes needed to use them.  We also created a ProvidersRepositoryGeneric class to load and manage providers.  But what good is a repository of providers if there is nothing to provide?  While the Provider model can be used for other things aside from data operations, this series is focusing on data access.  If you need a moment to go back and read Generics, Interfaces, Providers and You - Part 1:Getting Started feel free, we will be here when you get back.

At the end of part one I mentioned needing 4 more interfaces and breaking the interfaces into 4 sets, one for each of the crud operations: Create, Read, Update, Delete.  16 interfaces?  Yes, it's a lot and I have decided to do a fifth set that does all 4 operations. If you only want to do all 4 of the crud operations simply merge the interfaces together.

There are 4 crud operations and one to handle all four; This gives us 5 interfaces, ICreateDataObject, IReadDataObject, IUpdateDataObject, IDeleteDataObject and ICrudDataObject

First is ICreateDataObject...

public interface ICreateDataObject<ProviderType, DataObject>
        where ProviderType : ICreateProvider<ProviderType, DataObject>
        where DataObject : ICreateDataObject<ProviderType,DataObject>
    {
        void Save(IPrincipal user);
        void Save(ProviderType provider, IPrincipal user);

    }

As you can see the "create" interface defines a Save method and an overload.  Why the overload?  It has to do with object type casting, or boxing; we will come back to that a little in the series.  The interface takes 2 generic types, a ProviderType and the DataObject.  While we have not defined the ICreateProvider yet, we will soon and it is required here.  At first glance the where clauses seem to create a circular reference but they work.  More importantly they prevent type conversion errors that will stop you from being able to compile the code.

Next up, the IReadDataObject

public interface IReadDataObject<DataObject, IdType> 
where DataObject : IReadDataObject<DataObject, IdType> { IdType Id { get; } }

Again we have 2 generic types but they are not the same... well one of them is not.  Again we have a Dataobject with a where clause back to IReadDataObject, but there is no provider type this time.  It has been replaced with IdType.  While the class type of ContactInformation may need to read an instance of a ContactInformation, or a subclass, from the datastore an instance of ContactInformation will not.  Because an interface can not define static methods there is no need for the IReadDataObject Interface to know anything about the Provider.  By passing in the type of the Id field we do not need to change the interface, or create extra ones, if we need to use a Guid on some classes and an Integer on others.  This also allows you to use a class (or different classes) as an Id without changing the interfaces but that is a topic for a different article.

IUpdateDataObject

    public interface IUpdateDataObject<ProviderType, DataObject>
        where ProviderType : IUpdateProvider<ProviderType, DataObject>
        where DataObject : IUpdateDataObject<ProviderType, DataObject>
    {
        void Save(IPrincipal user);
        void Save(ProviderType provider, IPrincipal user);

    }

Yes, update looks the same as ICreateDataObject with different names.  You could change the method from Save to Update and Create in the interfaces.  I have found it is easier to use Save and check inside the save method if I am creating or updating and splitting the code path there.  Either way will work.

IDeleteDataObject

    public interface IDeleteDataObject<ProviderType,DataObject>
        where ProviderType : IDeleteProvider<ProviderType, DataObject>
        where DataObject : IDeleteDataObject<ProviderType,DataObject>
    {
        void Delete(IPrincipal user);
        void Delete(ProviderType provider, IPrincipal user);
    }

Nothing too surprising here, it looks very much like IUpdateDataObject and ICreateDataObject except instead of save it has 2 methods, "Delete".

In the save and delete methods an IPrincipal is expected.  This allows you to pass in  a user besides the current principle to check for access.  For example, you might have a custom security module that wraps the current user in a CompanyPrincipal and code in the data layer checks the principals role membership.  This would allow you to pass the user so you only check the users role membership instead of the CompanyPrincipals roles.

Since I would rather not have all four instances listed on my data object if it needs to do all the CRUD operations I will also create one more...

public interface ICrudDataObject<ProviderType, DataObject, IdType> 
: ICreateDataObject<ProviderType, DataObject>, IReadDataObject<DataObject, IdType>, IUpdateDataObject<ProviderType, DataObject>, IDeleteDataObject<ProviderType, DataObject> where ProviderType : ICrudProvider<ProviderType, DataObject, IdType> where DataObject : ICrudDataObject<ProviderType, DataObject, IdType> { }

This ICrudDataObject interface pulls together all 4 interfaces allowing a basic crud object to be created quickly and easily.

as the ICreateProvider, IReadProvider, IUpdateProvider and IDeleteProvider have already been mentioned I will also create the ICrudProvider now...

public interface ICrudProvider<ProviderType, DataObject, IdType> 
: ICreateProvider<ProviderType, DataObject>, IReadProvider<ProviderType, DataObject, IdType>, IUpdateProvider<ProviderType, DataObject>, IDeleteProvider<ProviderType, DataObject> where ProviderType : ICrudProvider<ProviderType, DataObject, IdType>
where DataObject : ICrudDataObject<ProviderType, DataObject, IdType>
{ }

Neither crud interface defines any properties or fields.  This is because everything is already defined on the other interfaces.  Speaking of other interfaces, let's turn our attention to the provider interfaces...ICreateProvider, IReadProvider, IUpdateProvider and IDeleteProvider.

The ICreateProvider defines a Create method.  There are no overloads at this point and the where clauses are the same as we have seen elsewhere.

public interface ICreateProvider<ProviderType, DataObject>
        where ProviderType : ICreateProvider<ProviderType, DataObject>
        where DataObject : ICreateDataObject<ProviderType,DataObject>
    {
        void Create(DataObject item, IPrincipal user);
    }

IReadDataObject defines a GetById method that return a type specified by the DataObject generic.

public interface IReadProvider<ProviderType, DataObject, IdType>
        where ProviderType : IReadProvider<ProviderType, DataObject, IdType>
        where DataObject : IReadDataObject<DataObject, IdType>
    {
        DataObject GetById(IdType id, IPrincipal user);
    }

IUpdateDataObject defines the method used to update an object.

    public interface IUpdateProvider<ProviderType, DataObject>
        where ProviderType : IUpdateProvider<ProviderType, DataObject>
        where DataObject : IUpdateDataObject<ProviderType,DataObject>
    {
        void Update(DataObject item, IPrincipal user);
    }

and finally IDeleteDataObject define the delete method.

public interface IDeleteProvider<ProviderType,DataObject>
        where ProviderType : IDeleteProvider<ProviderType, DataObject>
        where DataObject : IDeleteDataObject<ProviderType, DataObject>
    {
        void Delete(DataObject item,IPrincipal user);
    }

Coming Soon Generics, Interfaces, Providers and You - Part 3:IProviderCollection & IProviderRepository