Search code examples
c#entity-frameworkgenericsautomapperparametric-namespaces

Resolve DTO type into Domain type in WCF service


I have a WCF service which works with SQL through EntityFramework via net.tcp

Its allowed to clients to query items by Id from Db.

I have a plenty of methods which looks like this:

    public SiteDTO GetSiteById(long siteId)
    {
        using (var context = new MyDbContext())
        {
            var site = context.Site.Find(siteId);
            return AutoMapper.Mapper.Map<SiteDTO>(site);
        }
    }

so I decided to make The One Method to rule them all:

    public TDTO GetEntityById<TDTO, TSet>(object id)
        where TDTO : class
        where TSet : class
    {
        using (var context = new MyDbContext())
        {
            var entity = context.Set<TSet>().Find(id);
            if (entity == null)
                return default(TDTO);
            return AutoMapper.Mapper.Map<TSet, TDTO>(entity);
        }
    }

but the problem is that the client which should use it know nothing about TSet type (its a database type and clients only works with DTOs), so this method cannot be called that way. I need to make it like that:

    public TDTO GetEntityById<TDTO>(object id)
        where TDTO : class
    {
        using (var context = new MyDbContext())
        {
            //Something like this and a lot of reflection...
            Type setType = Resolver(typeof(TDTO)); 
            //I know this won't work. Just to show my intentions
            var entity = context.Set<setType>().Find(id); 
            if (entity == null)
                return default(TDTO);
            return AutoMapper.Mapper.Map<setType, TDTO>(entity);
        }
    }

I know how to solve the problem robust way - make Dictionary<Type,Type> register it one time and use it.

Question: Is there more elegance way (maybe with AutoMapper methods) to do it?


Solution

  • If you are ok with using a static resolver, then the following should work:

    public static class DTOResolver
    {
        public static void RegisterSetForDTO<TSet, TDTO>()
            where TDTO : class
            where TSet : class
        {
            DTOResolver<TDTO>.SetType = typeof(TSet);
            DTOResolver<TDTO>.SetMapper = new DTOResolver<TDTO>.Mapper<TSet>();
        }
    }
    public static class DTOResolver<TDTO> where TDTO : class
    {
        public abstract class Mapper
        {
            public abstract TDTO Map(Object entity);
        }
        public class Mapper<TSet> : Mapper
        {
            public override TDTO Map(Object entity)
            {
                return AutoMapper.Mapper.Map<TSet, TDTO>((TSet) entity);
            }
        }
        public  static  Type    SetType { get; set; }
        public  static  Mapper  SetMapper { get; set; }
    }
    

    Assuming DTOs and Sets like this:

    public class DTO1 {}
    public class Set1 {}
    public class DTO2 {}
    public class Set2 {}
    

    Register your mappings like this:

    static void Setup()
    {
        DTOResolver.RegisterSetForDTO<Set1, DTO1>();
        DTOResolver.RegisterSetForDTO<Set2, DTO2>();
    }
    

    And modify your GetEntityById like this:

    public TDTO GetEntityById<TDTO>(object id)
        where TDTO : class
    {
        using (var context = new MyDbContext())
        {
            var entity = context.Set(DTOResolver<TDTO>.SetType).Find(id);
            if (entity == null)
                return default(TDTO);
            return DTOResolver<TDTO>.SetMapper.Map(entity);
        }
    }
    

    The reason this works is that the DTOResolver<TDTO> defines a new static boundary in memory specific to a single TDTO type enabling us to register a single Type for the Set to use for that TDTO and a single Mapper subclass to instantiate a singleton for that is typed to a specific TSet.