我正在尝试使用dynamic
来解决因设计或缺乏而导致的不便(如果感兴趣Simplify method retrieving data from generic repository,可以在此处找到“不方便”)。
为了缩短它,我需要返回Entity
个实例的集合。上课非常简单:
[JsonObject]
public class Entity
{
[PrimaryKey]
[JsonProperty(PropertyName = "id")]
public virtual int Id { get; set; }
[JsonIgnore]
public string Content { get; set; }
}
因此Entity
只有Id
和Content
。继承类可能有其他属性,但我只对Content
部分(复杂JSON)感兴趣。
可以通过通用Repository<T>
访问各种不同的实体。我需要知道具体类的Type
,因为T
通过数据提供程序映射到基础SQLite表,构建在SQLite-net ORM之上。
因此,例如,如果我有Schedule : Entity
,那么我将使用Repository<Schedule>
来操作名为Schedule
的表。这部分工作正常。
// must be instantiated with concrete class/type inheriting
// from Entity in order to map to correct database table
public class Repository<T> where T : new()
{
public async virtual Task<IEnumerable<T>> GetAllAsync()
{
return await SQLiteDataProvider.Connection.Table<T>().ToListAsync();
}
// etc.
}
主要问题是“命令”来自JavaScript客户端,因此我将收到JSON格式的请求。在这个JSON中,我有一个名为CollectionName
的属性,它指定了所需的表(以及具体类型)。
我需要/想要的是一个很好的&amp;干净的代码片段,可以从任何给定的表中获取实体。因此,下面的方法应该解决我所有的问题,但事实证明它没有......
public async Task<IEnumerable<Entity>> GetAllEntitiesFrom(CollectionArgs args)
{
// args.CollectionName is type of entity as string
// namespace + collection name is mapped as correct type
// e.g. MyNamespace.Schedule
Type entityType = Type.GetType(
string.Format("{0}{1}", EntityNamespacePrefix, args.CollectionName), true, true);
// get correct repository type using resolved entity type
// e.g. Repository<MyNamespace.Schedule>
Type repositoryType = typeof(Repository<>).MakeGenericType(entityType);
dynamic repository = Activator.CreateInstance(repositoryType);
// Below `GetAllAsync()` returns `Task<IEnumerable<T>>`.
// this blocking call works 100%
//var entities = repository.GetAllAsync().Result;
// this non-blocking call works when it feels like it
var entities = await repository.GetAllAsync();
return entities;
}
因此,如果(上图)我使用阻止.Result
,那么一切都有吸引力。相反,如果我使用await
,代码可能会也可能不会。这似乎取决于Flying Spaghetti Monster的行星位置和/或情绪波动。
随机地,但通常情况下,给定的行将抛出
无法投射类型的对象 'System.Runtime.CompilerServices.TaskAwaiter'1 [System.Collections.Generic.IEnumerable'1 [MyNamespace.Schedule]]' 输入'System.Runtime.CompilerServices.INotifyCompletion'。
我正在使用.NET 4.0 Extended Framework。
答案 0 :(得分:3)
如果Repository<T>
类型是您自己制作的类型,则可以使其基于具有abstract Task<IEnumerable<Entity>> GetAllAsync()
的抽象基类型。然后,由于您的存储库显然已经有一个签名方法 - 所以你很好:
public abstract class Repository
{
public abstract Task<IEnumerable<Entity>> GetAllAsync();
}
然后让您的Repository<Entity>
基于存储库。
public class Repository<T>: Repository where T: Entity // Your existing class
{
public override async Task<IEnumerable<Entity>> GetAllAsync()
{
// Your existing implementation
}
//...existing stuff...
}
然后,当使用它而不是动态时,你可以说:
public async Task<IEnumerable<Entity>> GetAllEntitiesFrom(CollectionArgs args)
{
var entityType =
Type.GetType(
string.Format(
"{0}{1}",
EntityNamespacePrefix,
args.CollectionName),
true,
true);
var repositoryType =
typeof(Repository<>)
.MakeGenericType(entityType);
var repository =
(Repository) Activator
.CreateInstance( repositoryType );
return repository.GetAllAsync(); // await not required
}
根本没有动态。
答案 1 :(得分:1)
可以通过2次动态调用来实现:
public async Task<IEnumerable<Entity>> GetAllEntitiesFrom(CollectionArgs args)
{
var entityType = Type.GetType(
string.Format("{0}{1}", EntityNamespacePrefix, args.CollectionName), true, true);
var repositoryType = typeof(Repository<>).MakeGenericType(entityType);
var repository = Activator.CreateInstance(repositoryType);
var task = (Task)((dynamic)repository).GetAllAsync();
await task;
var entities = (IEnumerable<Entity>)((dynamic)task).Result;
return entities;
}
修改强>
虽然上述应该有效,但应该有更好的整体设计。不幸的是,MS决定使用任务进行异步,因为Task<TResult>
是类,我们不能从协方差中获益。但是,我们可以借助一点通用扩展来实现这一点,并且需要一点GC垃圾的成本。但IMO极大地简化了这种设计/实现。看看:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace Tests
{
// General async extensions
public interface IAwaitable<out TResult>
{
IAwaiter<TResult> GetAwaiter();
TResult Result { get; }
}
public interface IAwaiter<out TResult> : ICriticalNotifyCompletion, INotifyCompletion
{
bool IsCompleted { get; }
TResult GetResult();
}
public static class AsyncExtensions
{
public static IAwaitable<TResult> AsAwaitable<TResult>(this Task<TResult> task) { return new TaskAwaitable<TResult>(task); }
class TaskAwaitable<TResult> : IAwaitable<TResult>, IAwaiter<TResult>
{
TaskAwaiter<TResult> taskAwaiter;
public TaskAwaitable(Task<TResult> task) { taskAwaiter = task.GetAwaiter(); }
public IAwaiter<TResult> GetAwaiter() { return this; }
public bool IsCompleted { get { return taskAwaiter.IsCompleted; } }
public TResult Result { get { return taskAwaiter.GetResult(); } }
public TResult GetResult() { return taskAwaiter.GetResult(); }
public void OnCompleted(Action continuation) { taskAwaiter.OnCompleted(continuation); }
public void UnsafeOnCompleted(Action continuation) { taskAwaiter.UnsafeOnCompleted(continuation); }
}
}
// Your entity framework
public abstract class Entity
{
// ...
}
public interface IRepository<out T>
{
IAwaitable<IEnumerable<T>> GetAllAsync();
}
public class Repository<T> : IRepository<T> where T : Entity
{
public IAwaitable<IEnumerable<T>> GetAllAsync() { return GetAllAsyncCore().AsAwaitable(); }
protected async virtual Task<IEnumerable<T>> GetAllAsyncCore()
{
//return await SQLiteDataProvider.Connection.Table<T>().ToListAsync();
// Test
await Task.Delay(1000);
return await Task.FromResult(Enumerable.Empty<T>());
}
}
public static class Repository
{
public static IAwaitable<IEnumerable<Entity>> GetAllEntitiesFrom(string collectionName)
{
var entityType = Type.GetType(typeof(Entity).Namespace + "." + collectionName, true, true);
var repositoryType = typeof(Repository<>).MakeGenericType(entityType);
var repository = (IRepository<Entity>)Activator.CreateInstance(repositoryType);
return repository.GetAllAsync();
}
}
// Test
class EntityA : Entity { }
class EntityB : Entity { }
class Program
{
static void Main(string[] args)
{
var t = Test();
t.Wait();
}
static async Task Test()
{
var a = await Repository.GetAllEntitiesFrom(typeof(EntityA).Name);
var b = await Repository.GetAllEntitiesFrom(typeof(EntityB).Name);
}
}
}