Remove Dependency on DAAB Via Pluggable Database Helper Provider

Developer
Aug 24, 2007 at 5:10 PM
I like Enterprise Library, but is it possible that the P&P Team could be a bit more independent on its choice of database service providers so that I have the option of using EntLib?

Why couldn't the Repository Factory have the DAAB be a pluggable database provider, giving developers the option of using the default provider DAAB or possibly using another custom built provider?

Maybe the Repository Factory has an interface contract called IDatabase that provides database helper services and the DAAB becomes a provider of such services?

This would be useful for people who want to use the Repository Factory but don't want or can't deploy Enteprise Library in their production environment. I think as a whole, the various software factories need to think of EntLib as a pluggable library of services and not be dependent on it.

I added it as a votable work item:

Pluggable Database Helper Provider With DAAB as One Provider

Regards,

Dave

________________________________

David Hayden
Microsoft MVP C#
Developer
Aug 24, 2007 at 5:21 PM
I'm almost 100% behind you on this, but i think this issue is not of big importance before the community takes over this project.

But i will help you write code and tests to make this issue go away.

It shouldn't be the biggest task.

regards
Benny
Aug 24, 2007 at 6:46 PM
I'd actually go a different way here. The DAAB is already a pluggable database provider. Adding a second one on top of it is simply adding another layer for no real benefit. I'd rather not spend time writing yet another database isolation layer when I could be fixing the UI issues or other work. We've only got about two more weeks of budget left.

How often do you expect to need to switch "database service providers" at runtime? For me, that's always been a design time decision; I don't start with Linq and switch to Entlib in the middle of running. As such, wouldn't it be easier to change the T4's to generate code that calls either raw ADO.NET or your other database access system of choice?

There's really two sets of dependencies here: the dependencies of the generated code, and the dependencies of the guidance package itself. For the former, I can see wanting options, but, as I said, layering another abstraction on there is, to my mind, not the ideal solution here. For the latter, entlib does what the guidance package needs; I don't see any value on having to configure the GP itself to switch data access layers to read schemas.

Does that make sense?

-Chris

Developer
Aug 24, 2007 at 8:52 PM
I am not thinking about timelines and priorities, but mainly the importance of a pluggable database provider for the repository factory. I didn't realize there was a timeline on the project. I don't know what is on your priority list, so certainly this may not be the highest of priorities or give us the biggest bang for the buck.

Your thought process makes sense, but only if I choose to use Enterprise Library. In that case, adding another level of abstraction is redundant because Enterprise Library is pluggable. However, I don't always want to use Enterprise Library and therefore there is no extra layer really.

This is a problem I think P&P Faces. You think of Enterprise Library as your layer of abstraction, but that only rings true for someone who wants or can use Enterprise Library. P&P needs to think of Enteprise Library as a provider and not as their layer of abstraction in the factories. Forget about Enteprise Library and just code the factory like you would if Enterprise Library did not exist. In that case, tell me you wouldn't create a provider model around the database services? :) I think you would.

That being said, I don't know what is on your list of things to accomplish so maybe Benny and you are right, don't waste your time. I do, however, think that it is a legitimate concern. All in all, I love the changes and think you are doing a great job. Keep them coming.

Regards,

Dave

_______________________________

David Hayden
Microsoft MVP C#
Developer
Aug 25, 2007 at 1:55 PM
I have given this some time to sink in. And i disagree, it shouldn't be a pluggable modell. The receipt should handle this for u, is my opion. You should select which database-provider you would target with the repository, and then the receipts to use are selected after this. And you get some different solution targeting the solution you have selected.

2 clear candidates in the start.

x Use Enterprice Library Data Access Application Block
o Use ADO.net

Regards
Benny
Aug 25, 2007 at 6:35 PM
I strongly agree with Chris comment on this. The enterprise library has several of its application blocks designed with pluggable providers in mind. Any effort/requeriment needed to provide a wider support, should be sent to/implemented by the enterprise library team.

Benny, perhaps a clear path to follow would be to ease template configuration and selection upon recipe execution, enabling the type of extensions you are indicating. I think the community can help a lot after this is done, since group of templates might be shared, providing compatibility with existing database access frameworks.

Regards,
Freddy
Developer
Aug 25, 2007 at 7:57 PM
I feel with talk the same language there, Freddy

Regards
Benny
Developer
Aug 25, 2007 at 11:03 PM
I just got tired of talking about it and created a pluggable version.

Repository Factory With Pluggable Database Provider - 4 Hours Including Custom Tooling

Personally, I think it is better architected and much easier to deploy in different environments and for customers with different / simpler needs.

Regards,

Dave

________________________________

David Hayden
Microsoft MVP C#
Aug 26, 2007 at 12:18 AM
Edited Aug 26, 2007 at 12:19 AM
I agree with everyone but David here too. More levels of abstraction will not solve anything, particularly since the blocks already include a pluggable provider model. One of David's justifications on his blog (aside: why don't you allow comments on your blog posts?) was that the factories are often out-of-sync with the latest EntLib version. But even if the database provider were abstracted, it wouldn't "just work" - someone would need to build a new provider implementation that worked with the new version of EntLib, and this would almost certainly be more work than just updating the references in the existing approach.

Tom
Developer
Aug 26, 2007 at 2:45 AM
Enterprise Library provides a pluggable provider model, not the Repository Factory. Huge difference. Enterprise Library is a completely different product than the Repository Factory. Please don't mix the two.

One of my justifications ( plenty of other good ones, too ) is that I don't have to recompile the Repository Assembly. And it is true. I do not. The Repository Assembly does not care about Enterprise Library. If a new version of Enterprise Library comes out and you want to make it a provider, yes you will have to make it a provider, which is what I did in my example ( takes about 15 minutes ). Very simple. You also have the value of having several versions of Enterprise Library being separate providers. Bonus.

I am sorry, guys, but I won't budge on how I think about this and I think my solution speaks volumes. You can sit here and talk about how pluggable Enteprise Library is, but I thought this CodePlex Site was about the Repository Factory.

I am willing to just disagree, however. It was an interesting discussion and I think we got a lot out of it. If it doesn't happen, so be it :)

Regards,

Dave

_________________________________

David Hayden
Microsoft MVP C#
Aug 26, 2007 at 8:48 AM
My take:

Abstractions: You can have too much of a good thing

Tom
Developer
Sep 20, 2007 at 2:21 AM
Hi Tom,

I Agree.

My requirement was to to support a standard repository interface that would allow data access using either NHibernate or Enterprise Library data access. So we settled on the following interface

    public interface IDao<T, IdT>
    {
        T GetById(IdT id, bool shouldLock);
        List<T> GetAll();
        List<T> GetByExample(T exampleInstance, params string[] propertiesToExclude);
        T GetUniqueByExample(T exampleInstance, params string[] propertiesToExclude);
        T Save(T entity);
        T SaveOrUpdate(T entity);
        void Refresh(T entity);
        void Delete(T entity);
        void CommitChanges();
    }
Which when coupled with the following domain object Identifier interface:

    public interface IDomainIdentifier<IdT>
    {
        IdT ID { get; }
    }
Allows the RepositoryFactory class to be wrapped by a base class such that takes care of calling through to the appropriate methods. Note this class has a number of abstract properties to return classes that implement the various RepositoryFactory interfaces. In my case i implemented these as inner classes to avoid the proliferation of logic.

    /// <summary>
    /// Base class for defining Dao libraries
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="IdT">The type of the d T.</typeparam>
    public abstract class EntLibDaoBase<T, IdT> : IDao<T, IdT> where T : IDomainIdentifier<IdT>
    {
 
        /// <summary>
        /// Initializes a new instance of the <see cref="EntLibBaseDao&lt;T, IdT&gt;"/> class.
        /// </summary>
        /// <remarks>Creates a repository instance for performing database actions</remarks>
        /// <param name="databaseName">Name of the database.</param>
        protected EntLibDaoBase(string databaseName)
        {
            _repo = new Repository<T>(databaseName);
        }
 
        #region Protected Properites
        protected abstract IDomainObjectFactory<T> ObjectFactory { get; }
        protected abstract ISelectionFactory<IdT> SelectionFactoryById { get; }
        protected virtual ISelectionFactory<NullableIdentity> SelectionFactoryAll { get { throw new NotSupportedException(); } }
        protected virtual ISelectionFactory<T> SelectionFactoryByExample { get { throw new NotSupportedException(); } }
        protected abstract IInsertFactory<T, IdT> InsertFactory { get; }
        protected abstract IUpdateFactory<T> UpdateFactory { get; }
        protected abstract IDeleteFactory<IdT> DeleteFactory { get; }
        #endregion
 
        #region IDao<Listing,int> Members
 
        public T GetById(IdT id, bool shouldLock)
        {
            return _repo.FindOne<IdT>(SelectionFactoryById, ObjectFactory, id);
        }
 
        public List<T> GetAll()
        {
            return _repo.Find<NullableIdentity>(SelectionFactoryAll, ObjectFactory, new NullableIdentity());
        }
 
        public List<T> GetByExample(T exampleInstance, params string[] propertiesToExclude)
        {
            return _repo.Find<T>(SelectionFactoryByExample, ObjectFactory, exampleInstance);
        }
 
        public T GetUniqueByExample(T exampleInstance, params string[] propertiesToExclude)
        {
            return _repo.FindOne<T>(SelectionFactoryByExample, ObjectFactory, exampleInstance);
        }
 
        /// <summary>
        /// Saves the specified entity.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <returns></returns>
        /// <remarks>
        /// Designed to save new objects objects that have specified their own identifier.
        /// </remarks>
        public T Save(T entity)
        {
            _repo.Save(UpdateFactory, entity);
            return entity;
        }
 
        /// <summary>
        /// Saves or updates the entity.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <returns></returns>
        /// <remarks>
        /// Designed to save new objects and set their identity, or update an existing object
        /// </remarks>
        public T SaveOrUpdate(T entity)
        {
            IdT defaultID = default(IdT);
            if (GetEntityId(entity).Equals(defaultID))
            {
                _repo.Add(InsertFactory, entity);
            }
            else
            {
                _repo.Save(UpdateFactory, entity);
            }
            return entity;
        }
 
        public void Refresh(T entity)
        {
            _repo.FindOne<IdT>(SelectionFactoryById, ObjectFactory, GetEntityId(entity));
        }
 
        public void Delete(T entity)
        {
            _repo.Remove<IdT>(DeleteFactory, GetEntityId(entity));
        }
 
        public void CommitChanges()
        {
            throw new Exception("The method or operation is not implemented.");
        }
 
        #endregion
 
        /// <summary>
        /// Returns the entity id
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <returns></returns>
        private IdT GetEntityId(T entity)
        {
            IdT id = ((IDomainIdentifier<IdT>)entity).ID;
            return id;
        }
 
        private Repository<T> _repo; // Private repository object
 
    }