问题
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?
回答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>;
}
}
回答2:
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 MyExtensions
class!
来源:https://stackoverflow.com/questions/21929671/generics-static-classes-implement-query-helper-method