I am somewhat new to Generics and I came across this issue, I have repetitive code that I am trying to clean up. The signature is different but the code being executed is the same, is there a way to pass in a generic type instead of having to specify each type in a new signature?
public JsonResult<JsonData> GetServiceData(Func<IServiceResponse<IEnumerable<Order>>> func)
{
var response = func();
var jsonDataContainer = Mapper.Map<JsonData>(response);
var result = GetJsonResult(jsonDataContainer);
return result;
}
public JsonResult<JsonData> GetServiceData(Func<IServiceResponse<List<int>>> func)
{
var response = func();
var jsonDataContainer = Mapper.Map<JsonData>(response);
var result = GetJsonResult(jsonDataContainer);
return result;
}
public JsonResult<JsonData> GetServiceData(Func<IServiceResponse<User>> func)
{
var response = func();
var jsonDataContainer = Mapper.Map<JsonData>(response);
var result = GetJsonResult(jsonDataContainer);
return result;
}
It's hard to answer this question definitively, because you haven't specified the signature of Mapper.Map.
But, if Mapper.Map can take an IServiceResponse<T> of any type T, then this would work.
public JsonResult<JsonData> GetServiceData<T>(Func<IServiceResponse<T>> func)
{
IServiceResponse<T> response = func();
var jsonDataContainer = Mapper.Map<JsonData>(response);
var result = GetJsonResult(jsonDataContainer);
return result;
}
Related
I have some problem with redis generic caching because redis stores values as json and I have to deserialize it into my models but I can not because I'm using generic methods and I couldnt manage to solve this problem.
Redis get operations:
public T Get<T>(string key)
{
var type = typeof(T);
var result = default(object);
RedisInvoker(x => { result = x.Get<object>(key); });
var settings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
var deserializedObj = JsonConvert.DeserializeObject<T>(result.ToString(), settings);
return deserializedObj;
}
public object Get(string key)
{
var result = default(object);
RedisInvoker(x => { result = x.Get<object>(key); });
return result;
}
Caching Interception :
public override void Intercept(IInvocation invocation)
{
var methodName = string.Format($"{invocation.Method.ReflectedType.FullName}.{invocation.Method.Name}");
var arguments = invocation.Arguments.ToList();
var key = $"{methodName}({string.Join(",", arguments.Select(x => x?.ToString() ?? "<Null>"))})";
if (_cacheManager.IsAdded(key))
{
invocation.ReturnValue = _cacheManager.Get(key);
return;
}
invocation.Proceed();
_cacheManager.Add(key, invocation.ReturnValue, _duration);
}
What I want to do is get the method's returnType which is also generic and send it to Get method with generic type. But I couldn't send it like this :
var returnType = invocation.Method.ReturnType;
if (_cacheManager.IsAdded(key))
{
invocation.ReturnValue = _cacheManager.Get<returnType>(key);
return;
}
The error is:
returnType is a variable but used like a type
So you want to call .Get<T> with a type that you only know at runtime. The easy answer is; "don't". Instead create a non-generic method that you can call easily;
public object Get(Type type, string key) {...}
public T Get<T>(string key) => (T)Get(typeof(T), key);
Because the alternative involves reflection;
invocation.ReturnValue = typeof(...)
.GetMethod("Get")
.MakeGenericMethod(returnType)
.Invoke(instance, new object[]{ key });
This question already has answers here:
Generic Method Executed with a runtime type [duplicate]
(5 answers)
Closed 2 years ago.
I have a DbContext with a property Countries
public DbSet<Country> Countries { get; set; }
I want to use reflection to call the ToListAsync method for each type in my context
Im working towards a method that will take a snapshot of all the tables in a DbContext including filtering expressions. The filtering is already dealt with using the EntityQueryFilterHelper
I am using Ef Core for .NET Core 3.1
Thanks to the help in the answer I now have the code
var contextDictionary = new Dictionary<string, object>();
var allCountries = await this.Countries.ToListAsync();
var firstCountry = allCountries.FirstOrDefault();
var filters = new List<FilterObject>
{
new FilterObject() {PropertyName = "Name", PropertyValue = firstCountry.Name}
};
var entityTypes = this.Model.GetEntityTypes().Where(x => x.Name.Contains("Country"));
foreach (var entityType in entityTypes)
{
var requiredType = entityType.GetType();
var filter = EntityQueryFilterHelper.CreateFilter<Country>(filters);
var allItems = from x in this.Countries select x;
var filteredItems = filter(allItems);
var method = typeof(EntityFrameworkQueryableExtensions).GetMethod(nameof(EntityFrameworkQueryableExtensions.ToListAsync))?.MakeGenericMethod(typeof(Country));
var toListAsyncResult = (Task<List<Country>>) method?.Invoke(null, new object[] {filteredItems, default(CancellationToken)});
if (toListAsyncResult != null)
{
var result = await toListAsyncResult;
contextDictionary.Add("Countries", result);
}
}
This works perfectly - so the next question is how to change the above to make use of the EntityType instead of it being hard coded
Can anyone help please?
Paul
The ToListAync() is in the class EntityFrameworkQueryableExtensions, so
var method = typeof(EntityFrameworkQueryableExtensions).GetMethod(nameof(EntityFrameworkQueryableExtensions.ToListAsync)).MakeGenericMethod(typeof(Country));
and it is static, so:
var result = method.Invoke(null, new object[] { this.Countries, default(CancellationToken) });
Where result is a Task<List<Country>> that is a Task.
To call it is hard :-) Technically calling the method is easy... extracting the result is hard. If you want to go full-dynamic it gets hard quickly:
var t = (Task)method.Invoke(null, new object[] { context.Blogs, default(CancellationToken) });
await t;
var ilist = (IList)((dynamic)t).Result;
and now you a non-generic IList, so its elements are object. But in the end you are going in the wrong direction. If you want to go in the "right" direction, make a :
public static async void DoWorkOnMyIQueryable<T>(IQueryable<T> dbset)
{
List<T> res = await dbset.ToListAsync();
foreach (var el in res)
{
// Do something
}
}
and call the DoWorkOnMyIQueryable through reflection. Now at least you have a typed List<T>!
Now that you have written your full question, as often it is, it was an XY problem.
As I've written, it is much easier to build a single generic method that is based around a generic TEntity and then use reflection to call it.
The method:
public static class Tools
{
public static async Task<IList> ExtractData<TEntity>(DbContext context, List<FilterObject> filters) where TEntity : class
{
var dbset = context.Set<TEntity>();
var filter = EntityQueryFilterHelper.CreateFilter<TEntity>(filters);
var filtered = filter(dbset);
return await filtered.ToListAsync();
}
}
And now to call it:
public async Task<Dictionary<string, IList>> GenerateContextDictionary()
{
var filters = new List<FilterObject>();
// Here prepare your filters
var entityTypes = this.Model.GetEntityTypes().Where(x => x.Name.Contains("Country"));
var method = typeof(Tools).GetMethod(nameof(Tools.ExtractData), BindingFlags.Public | BindingFlags.Static);
var contextDictionary = new Dictionary<string, IList>();
foreach (var entityType in entityTypes)
{
var method2 = method.MakeGenericMethod(entityType.ClrType);
var ilist = await (Task<IList>)method2.Invoke(null, new object[] { this, filters });
contextDictionary.Add(entityType.Name, ilist);
}
return contextDictionary;
}
In code we found a lot of duplicate code that call function from Context then return a list result. If result is null, return null. Otherwise, return list result.
public static IList<RemittanceDetail> GetDetails()
{
var context = StartupBusinessLogic.CreateContext();
var sourceResult = context.SP_GetRemittanceDetails();
if (sourceResult == null)
return null;
IList<RemittanceDetail> result = new List<RemittanceDetail>();
Mapper.Map(sourceResult, result);
return result;
}
public static IList<PaymentType> GetPaymentTypes()
{
var context = StartupBusinessLogic.CreateContext();
var sourceResult = context.SP_GetPaymentTypes();
if (sourceResult == null)
return null;
IList<PaymentType> result = new List<PaymentType>();
Mapper.Map(sourceResult, result);
return result;
}
Could we create a generic method to handle this task? My concern is how to call dynamic function of context
context.SP_GetPaymentTypes()
The problem you have right now is that you're using 2 functions to fetch some data in my opinion, why don't you move all the logic from your current functions to SP_GetRemittanceDetails and SP_GetPaymetTypes. In those functions you should just return a list, by calling .ToList() method in the end of your query. It could look like this:
public IList<PaymentType> SP_GetPaymentTypes()
{
using(var ctx = new StartupBusinessLogic())
{
var query = ctx. blablabla
query = query.*more filtering*
return query.ToList();
}
}
2 remarks with this:
1) It's better to call your context in a using statement, because at the moment you're not disposing of your context which is bad.
2) When your query result contains nothing, and you call ToList(), the List you return will automatically be null, so the checks and use of Mapping function becomes obsolete.
To convert the IQueryable to IList you can use extension methods:
public static class Extensions
{
public static IList<T> GetList<T>(this IQueryable<T> query)
{
return query.ToList();
}
}
Then you can simply do
var context = StartupBusinessLogic.CreateContext();
var sourceResult = context.SP_GetRemittanceDetails().GetList();
And the result will be of type Ilist<T>
I'm attempting to write a simple generic cache but running into problems with generating unique enough keys with using System.Func as a callback.
What I ideally want is to be able to pass in an invocable delegate of some description so that the cache itself can get the value, and determine a key all from the same expression. Right now I'm getting exceptions because I'm not passing in an argument that implements or inherits from MethodCallExpression. What should I be using instead of a System.Func for this intended behaviour?
public class SimpleCacheKeyGenerator : ICacheKey
{
public string GetCacheKey<T>(Expression<Func<T>> action)
{
var body = (MethodCallExpression) action.Body; //!!! Exception Raised - action.Body is FieldExpression
ICollection<object> parameters = (from MemberExpression expression in body.Arguments
select
((FieldInfo) expression.Member).GetValue(
((ConstantExpression) expression.Expression).Value)).ToList();
var sb = new StringBuilder(100);
sb.Append(body.Type.Namespace);
sb.Append("-");
sb.Append(body.Method.Name);
parameters.ToList().ForEach(x =>
{
sb.Append("-");
sb.Append(x);
});
return sb.ToString();
}
}
public class InMemoryCache : ICacheService
{
private readonly ICachePolicy _cachePolicy;
private readonly ICacheKey _cacheKey;
public InMemoryCache(ICachePolicy cachePolicy, ICacheKey cacheKey)
{
_cachePolicy = cachePolicy;
_cacheKey = cacheKey;
}
public T Get<T>(Func<T> getItemCallback) where T : class
{
var cacheID = _cacheKey.GetCacheKey(() => getItemCallback);
var item = HttpRuntime.Cache.Get(cacheID) as T;
if (item == null)
{
item = getItemCallback();
if (_cachePolicy.RenewLeaseOnAccess)
{
HttpContext.Current.Cache.Insert(cacheID, getItemCallback, null, System.Web.Caching.Cache.NoAbsoluteExpiration, _cachePolicy.ExpiresAfter);
}
else
{
HttpContext.Current.Cache.Insert(cacheID, getItemCallback, null, DateTime.UtcNow + _cachePolicy.ExpiresAfter, System.Web.Caching.Cache.NoSlidingExpiration);
}
}
return item;
}
}
The problem is, you can't easily use both the Expression> and Func representing the same thing without duplicating the code.
You could possibly convert Expression> to a Func with LambdaExpression>.Compile() method, but that could create a performance problem, since Compile actually uses assembly emit, which is quite expensive.
Here is how i would implement the same thing without using Expressions and compilation.
You can find the same pattern everywhere in the standard Linq extensions.
Pass your argument as a separate object.
The type you use as an argument will be used for type inference for the delegate, and the argument itself will provide the arguments for the delegate at the same type.
Note that the cache in this implementation works because of the default ToString implementation of the anonimous objects used as arguments.
void Main()
{
var computeCount = 0;
var item1 = GetCached(new{x = 1, y = 2}, (arg)=>{computeCount++; return arg.x + arg.y;});
Console.WriteLine(item1);
var item2 = GetCached(new{x = 1, y = 2}, (arg)=>{computeCount++; return arg.x + arg.y;});
Console.WriteLine(item2);
var item3 = GetCached(new{x = 1, y = 3}, (arg)=>{computeCount++; return arg.x + arg.y;});
Console.WriteLine(item3);
Console.WriteLine("Compute count:");
Console.WriteLine(computeCount);
}
Dictionary<string, object> _cache = new Dictionary<string, object>();
E GetCached<T, E>(T arg, Func<T,E> getter)
{
// Creating the cache key.
// Assuming T implements ToString correctly for cache to work.
var cacheKey = arg.ToString();
object result;
if (!_cache.TryGetValue(cacheKey, out result))
{
var newItem = getter(arg);
_cache.Add(cacheKey, newItem);
return newItem;
}
else
{
Console.WriteLine("Cache hit: {0}", cacheKey);
}
return (E)result;
}
Console output:
3
Cache hit: { x = 1, y = 2 }
3
4
Compute count:
2
You get this exception because (() => getItemCallback) means (() => { return getItemCallback; })
That's why action.Body is not a method call, it is the return statement. If you change your code to (() => getItemCallback()) you should not have the error. But you won't have any arguments.
To obtain arguments of the base call, you will have to change your code to accept an Expression and Compile your lambda.
public T Get<T>(Expression<Func<T>> getItemCallbackExpression) where T : class
{
var cacheID = _cacheKey.GetCacheKey(getItemCallbackExpression);
var item = HttpRuntime.Cache.Get(cacheID) as T;
if (item == null)
{
item = getItemCallback.Compile()();
if (_cachePolicy.RenewLeaseOnAccess)
{
HttpContext.Current.Cache.Insert(cacheID, getItemCallback, null, System.Web.Caching.Cache.NoAbsoluteExpiration, _cachePolicy.ExpiresAfter);
}
else
{
HttpContext.Current.Cache.Insert(cacheID, getItemCallback, null, DateTime.UtcNow + _cachePolicy.ExpiresAfter, System.Web.Caching.Cache.NoSlidingExpiration);
}
}
return item;
}
I won't recommend this approach because compiling an expression takes time.
It may be easier and more performant to manually generate cache keys. If you really want to automatically manage cache keys. You may have a look to Aspect Oriented Programmation using castle.Core or PostSharp. Theses tools will allow you to automatically add code to some of your methods and automatically add cache logic.
I modified the code as below, I got the expected result this way, so you can try this, I hope this would be helpful.
public class SimpleCacheKeyGenerator
{
public string GetCacheKey<T, TObject>(Expression<Func<T, TObject>> action)
{
var body = (MethodCallExpression) action.Body;
ICollection<object> parameters = body.Arguments.Select(x => ((ConstantExpression) x).Value).ToList();
var sb = new StringBuilder(100);
sb.Append(body.Type.Namespace);
sb.Append("-");
sb.Append(body.Method.Name);
parameters.ToList().ForEach(x =>
{
sb.Append("-");
sb.Append(x);
});
return sb.ToString();
}
}
public class InMemoryCache
{
public void Get<T, TObject>(Expression<Func<T, TObject>> getItemCallback)
{
var generator = new SimpleCacheKeyGenerator();
Console.WriteLine(generator.GetCacheKey(getItemCallback));
}
}
main:
private static void Main(string[] args)
{
var cache = new InMemoryCache();
var tt = new SomeContextImpl();
cache.Get<SomeContextImpl, string>(x => x.Any("hello", "hi"));
Console.ReadKey();
}
somcontextimpl:
public class SomeContextImpl
{
public string Any(string parameter1, string parameter2) { return ""; }
}
How can I get the HttpGet function below to populate all of the responses (denoted by #2)? I cannot seem to declare a collection of the appropriate type to handle adding each response. Everything I've tried so far has given a compiler error:
'Cannot convert expression type null to return type T`.
C#/ASP.NET/MVC 5.1/.NET 4.5
public async Task<IList<T>> Query<T>(string query)
{
var response = await _serviceHttpClient.HttpGet<IList<T>>(string.Format("query?q={0}", query), "records");
return response;
}
public async Task<T> HttpGet<T>(string urlSuffix, string nodeName)
{
// #1 var list = ???
var r = new QueryResult<T>();
do
{
r = await HttpGetInternal<T>(urlSuffix, nodeName);
// #2 list = r.Records
} while (!string.IsNullOrEmpty(r.NextRecordsUrl));
return // #3 list;
}
private async Task<QueryResult<T>> HttpGetInternal<T>(string urlSuffix, string nodeName)
{
var url = Common.FormatUrl(urlSuffix, _instanceUrl, _apiVersion);
var request = new HttpRequestMessage()
{
RequestUri = new Uri(url),
Method = HttpMethod.Get
};
request.Headers.Add("Authorization", "Bearer " + _accessToken);
var responseMessage = await _httpClient.SendAsync(request);
var response = await responseMessage.Content.ReadAsStringAsync();
if (responseMessage.IsSuccessStatusCode)
{
var jObject = JObject.Parse(response);
var jToken = jObject.GetValue(nodeName);
var res = JsonConvert.DeserializeObject<T>(jToken.ToString());
var r = new QueryResult<T>()
{
NextRecordsUrl = jObject.GetValue("nextRecordsUrl").ToString(),
Records = res
};
return r;
}
var errorResponse = JsonConvert.DeserializeObject<ErrorResponses>(response);
throw new ForceException(errorResponse[0].errorCode, errorResponse[0].message);
}
public class QueryResult<T>
{
public int TotalSize { get; set; }
public bool Done { get; set; }
public string NextRecordsUrl { get; set; }
public T Records { get; set; }
}
I would change the signature to return IList<T> the same as the method that is calling it since there isn't an easy way to create something that implements the interface specified by the generic. Another alternative would be to call it with a concrete type, say List<T>, which implements the return interface of the caller, but I think the other way is cleaner.
Since r.Records is of type T it should be able to be added to the List<T>. List<T> implements IList<T> and thus satisfies the return type.
public async Task<IList<T>> Query<T>(string query)
{
var response = await _serviceHttpClient.HttpGet<T>(string.Format("query?q={0}", query), "records");
return response;
}
public async Task<IList<T>> HttpGet<T>(string urlSuffix, string nodeName)
{
var list = new List<T>();
do
{
var r = await HttpGetInternal<T>(urlSuffix, nodeName);
list.Add(r.Records);
}
while (!string.IsNullOrEmpty(r.NextRecordsUrl));
return list;
}