0

Currently we implement a mapping service like this (the service uses automapper, and we make use of the projection feature on it for this part)

// Injected
// IGenericRepository<Entity> entityRepo

 var query = this.entityRepo
                 .FindAll(a => a.Id == someId)
                 .Take(1);

 var result = this.mappingService
                  .Map<Entity, EntityDto>(query)
                  .FirstOrDefault();

I'd like to create an extension that would allow me to do the following

var result = this.entityRepo
                 .FindAll(a => a.Id == someId)
                 .Take(1).Map<EntityDto>()   <--- Entity inferred from repo type
                 .FirstOrDefault();

My current attempt:

 public static class IQueryableExtensions
 {
     private static IMappingService mappingService;

     // will need to be called in app initialization
     public static void InitialiseMapper(IMappingService service)
     {
         mappingService = service;
     }

     public static IEnumerable<TDto> Map<TAttribute, TDto>(this IQueryable<TAttribute> value)
            where TDto : class
            where TAttribute : IEntity
     {
        return mappingService.Map<TAttribute, TDto>(value);
     }
 }

Thus currently my implementation would look like this.

var result = this.entityRepo
                     .FindAll(a => a.Id == someId)
                     .Take(1).Map<Entity,EntityDto>()
                     .FirstOrDefault();

Questions:

1) How would i go about inferring the entity type from the IQueryable object

2) I realize i cant create a constructor that takes parameters, when creating a static class. Is the way i init the mapper the best/only way?

dcastro
  • 66,540
  • 21
  • 145
  • 155
Rohan Büchner
  • 5,333
  • 4
  • 62
  • 106
  • 1
    I dont know if it is just me, and if I am reading this wrong. Why exactly are you using `.Take(1)` and `.FirstOrDefault()`, would you not be able to just go `.FirstOrDefault(a => a.Id == someId).Map`? Also can't you change your `Map()` method and change it to `MapFirstOrDefault()` and inside your helper class do the first or default, since you already going through the effort of writing an extension method. – jacqijvv Feb 21 '14 at 09:08
  • Take(1), will still return a collection and FirstOrDefault(), grabs the first item and maps it to your entity type. But in this example the Take(1), & FirstOrDefault() is irrelevant to the question, merely as an example of a query... as my question relates to the mapping part EG. IQueryable to IEnumerable – Rohan Büchner Feb 21 '14 at 09:19
  • So let me see if I get this right this time around? So you're saying that this code `return mappingService.Map(value);` is not returning an `IEnumerable`, but you would like it to return `IEnuerable`? – jacqijvv Feb 21 '14 at 09:22
  • I'd like to change the calling syntax from: .Map(query) to .Map() – Rohan Büchner Feb 21 '14 at 09:25

2 Answers2

1

1) Currently, you simply can't do that in C#. The type inference is not good enough. You can either specify all type parameters or none of them.

Edit: If you really want the version with a single parameter, you have to delete the second type parameter, type the parameter as non-generic IQueryable and deal with it. One way of doing that would be to determine the generic IQueryable<T> type at runtime. However, this requires reflection. In the case of IQueryable, you can also use the query provider to get around the reflection.

2) You can use a static type constructor.

public static class MyExtensions {
    static MyExtensions() {
        //initialization goes here
    }
}

This type constructor is even called thread-safe. However, if you manage to throw an exception here, you cannot access the MyExtensionsclass!

Georg
  • 5,626
  • 1
  • 23
  • 44
  • The query provider approach is similar to http://stackoverflow.com/questions/21754638/invoking-orderby-system-linq-enumerable-with-reflection/21755429#21755429 – Georg Feb 21 '14 at 09:28
1

I tried that with reflection. The constraints are only for demo. If you want to call the reflection code multiple times be sure to cache the final methodinfo.

void Main()
{
    var a = new Entity[] {new Entity { name = "a"},new Entity { name = "b"}};

    Console.WriteLine(a.Take(1).Map<EntityDto>());
}

public class Entity
{
    public string name;
}

public class EntityDto
{
    public string dtoname;

}

public static class EntityExtensions
{
    public static IEnumerable<U> Map<T,U>(this IEnumerable<T> e) where T: Entity where U: EntityDto, new()
    {
        foreach(var a in e)
        {
            yield return new U() { dtoname = a.name };
        }
    }


    public static IEnumerable<U> Map<U>(this IEnumerable<object> e)
    {
        var method = typeof(EntityExtensions).GetMethods(BindingFlags.Static | BindingFlags.Public)     
        .Where(m => m.Name == "Map" && m.GetGenericArguments().Length == 2)
        .Single();
        method = method.MakeGenericMethod(e.GetType().GetGenericArguments()[0], typeof(U));

        return method.Invoke(null, new object[] { e}) as IEnumerable<U>;
    }
}
stepandohnal
  • 457
  • 4
  • 13