1

I'm struggling with implementing a factory object. Here's the context :

I've in a project a custom store. In order to read/write records, I've written this code in a POCO model/separated repository:

public class Id { /* skip for clarity*/} // My custom ID representation

public interface IId
{
    Id Id { get; set; }
}
public interface IGenericRepository<T> where T : IId
{
    T Get(Id objectID);
    void Save(T @object);
}
public interface IContext
{
    TRepository GetRepository<T, TRepository>() 
        where TRepository : IGenericRepository<T> 
        where T:IId;
    IGenericRepository<T> GetRepository<T>() 
        where T:IId;
}

My IContext interface defines two kind of repositories. The former is for standard objects with only get/save methods, the later allows me to define specifics methods for specific kind of objects. For example :

public interface IWebServiceLogRepository : IGenericRepository<WebServiceLog>
{
    ICollection<WebServiceLog> GetOpenLogs(Id objectID);
}

And it the consuming code I can do one of this :

  • MyContext.GetRepository<Customer>().Get(myID); --> standard get
  • MyContext.GetRepository<WebServiceLog, IWebServiceLogRepository>().GetOpenLogs(myID); --> specific operation

Because most of objects repository are limited to get and save operations, I've written a generic repository :

public class BaseRepository<T> : IGenericRepository<T>
    where T : IId, new()
{
    public virtual T Get(Id objectID){ /* provider specific */ }
    public void Save(T @object) { /* provider specific */ }
}

and, for custom ones, I simply inherits the base repository :

internal class WebServiceLogRepository: BaseRepository<WebServiceLog>, IWebServiceLogRepository
{
    public ICollection<WebServiceLog> GetByOpenLogsByRecordID(Id objectID)
    {
        /* provider specific */
    }
}

Everything above is ok (at least I think it's ok). I'm now struggling to implement the MyContext class. I'm using MEF in my project for other purposes. But because MEF doesn't support (yet) generic exports, I did not find a way to reach my goal.

My context class is looking like by now :

[Export(typeof(IContext))]
public class UpdateContext : IContext
{
    private System.Collections.Generic.Dictionary<Type, object> m_Implementations;

    public UpdateContext()
    {
        m_Implementations = new System.Collections.Generic.Dictionary<Type, object>();
    }
    public TRepository GetRepository<T, TRepository>()
        where T : IId
        where TRepository : IGenericRepository<T>
    {
        var tType = typeof(T);
        if (!m_Implementations.ContainsKey(tType))
        {
            /* this code is neither working nor elegant for me */
            var resultType = AppDomain.CurrentDomain.GetAssemblies().SelectMany(
                (a) => a.GetTypes()
                ).Where((t)=>t.GetInterfaces().Contains(typeof(TRepository))).Single();

            var result = (TRepository)resultType.InvokeMember("new", System.Reflection.BindingFlags.CreateInstance, null, null, new object[] { this });

            m_Implementations.Add(tType, result);
        }
        return (TRepository)m_Implementations[tType];
    }

    public IGenericRepository<T> GetRepository<T>() where T : IId
    {
        return GetRepository<T, IGenericRepository<T>>();
    }
}

I'd appreciate a bit of help to unpuzzle my mind with this quite common scenario

Steve B
  • 34,941
  • 18
  • 92
  • 155
  • if you use a container such as Windsor or Unity you wouldn't need to write any of this code yourself – Jason Apr 18 '11 at 09:27
  • @Jason: can you develop a bit ? and as I'm already using MEF, I'd like to avoid using another composition framework. Isn't a pure c# code to apply to my case ? – Steve B Apr 18 '11 at 09:30
  • i think this thread: http://stackoverflow.com/questions/130794/what-is-dependency-injection is a good place to start with understanding why you'd want to use a DI container. among other things they replace the need to develop your own factory classes – Jason Apr 18 '11 at 09:38

1 Answers1

1

Not sure if I've understood you correctly, but I think you're perhaps over complicating things. To begin with, make sure you've designed your code independent of any factory or Dependency Injection framework or composition framework.

For starters lets look at what you want your calling code to look like, this is what you said:

MyContext.GetRepository<Customer>().Get(myID); --> standard get
MyContext.GetRepository<WebServiceLog, IWebServiceLogRepository>().GetOpenLogs(myID);

You don't have to agree with my naming choices below, but it indicates what I undertand from your code, you can tell me if I'm wrong. Now, I feel like the calling would be simpler like this:

RepositoryFactory.New<IRepository<Customer>>().Get(myId);
RepositoryFactory.New<IWebServiceLogRepository>().GetOpenLogs(myId);

Line 1: Because the type here is IRepository it's clear what the return type is, and what the T type is for the base IRepository.

Line 2: The return type here from the factory is IWebServiceLogRepository. Here you don'y need to specify the entity type, your interface logically already implements IRepository. There's no need to specify this again.

So your interface for these would look like this:

public interface IRepository<T>
{
T Get(object Id);
T Save(T object);
}

public interface IWebServiceLogRepository: IRepository<WebServiceLog>
{
List<WebServiceLog> GetOpenLogs(object Id);
}

Now I think the implementations and factory code for this would be simpler as the factory only has to know about a single type. On line 1 the type is IRepository, and in line 2, IWebServiceLogRepository.

Try that, and try rewriting your code to simply find classes that implement those types and instantiating them.

Lastly, in terms of MEF, you could carry on using that, but Castle Windsor would really make things much simpler for you, as it lets you concentrate on your architecture and code design, and its very very simple to use. You only ever reference Castle in your app startup code. The rest of your code is simply designed using the Dependency Injection pattern, which is framework agnostic.

If some of this isn't clear, let me know if you'd like me to update this answer with the implementation code of your repositories too.

UPDATE

and here's the code which resolves the implementations. You were making it a bit harder for yourself by not using the Activator class.

If you use Activator and use only one Generic parameter as I've done in the method below, you should be ok. Note the code's a bit rough but you get the idea:

public static T GetThing<T>()
        {
            List<Type> assemblyTypes = AppDomain.CurrentDomain.GetAssemblies()
                                        .SelectMany(s => s.GetTypes()).ToList();

            Type interfaceType = typeof(T);

            if(interfaceType.IsGenericType)
            {
                var gens = interfaceType.GetGenericArguments();
                List<Type> narrowed = assemblyTypes.Where(p => p.IsGenericType && !p.IsInterface).ToList();
                var implementations = new List<Type>();
                narrowed.ForEach(t=>
                {
                    try
                    {
                        var imp = t.MakeGenericType(gens);
                        if(interfaceType.IsAssignableFrom(imp))
                        {
                            implementations.Add(imp);     
                        }  
                    }catch
                    {
                    }
                });

                return (T)Activator.CreateInstance(implementations.First());
            }
            else
            {
                List<Type> implementations = assemblyTypes.Where(p => interfaceType.IsAssignableFrom(p) && !p.IsInterface).ToList();

                return (T)Activator.CreateInstance(implementations.First());
            }

        }
andy
  • 8,165
  • 12
  • 71
  • 120
  • thanks for your long answer. You said `Try that, and try rewriting your code to simply find classes that implement those types and instantiating them.`. It's ***exactly*** what I was trying to simplify. The api signatures you show are quite near from I've done. I've chosen to have `MyContext.GetRepository` while you suggest to have `RepositoryFactory.New`. In both case, how to implement this methods ? Both approaches still require to find implementations... And I'm still don't know how to resolve generic implementations using MEF or a pure C# code – Steve B Sep 10 '11 at 11:58
  • ahh ok steve, my bad, let me get back to you. I'll provide pure C# code as I've never used MEF – andy Sep 10 '11 at 23:27
  • I'll give a try. I'm just afraid of this code : `AppDomain.CurrentDomain.GetAssemblies()` as I think ASP.Net load assemblies on demand, and not at app startup – Steve B Sep 13 '11 at 19:02
  • well, this is just the code on how to do it. You should try and fit it best into your own code now. There are plenty of optimizations you could make such as caching the types, getting only some types. windsor for example does most of its hard work at start, but its the devs job to put the code into the startup section – andy Sep 13 '11 at 20:46