2015-08-18 18 views
5

Sto tentando di utilizzare dynamic per aggirare gli inconvenienti causati dalla progettazione o dalla mancanza di esso (il "disagio" può essere trovato qui, se interessato Simplify method retrieving data from generic repository).Comportamento incoerente quando si utilizza await con tipo dinamico

Per farla breve, devo restituire la raccolta delle istanze Entity. Classe è piuttosto semplice:

[JsonObject] 
public class Entity 
{ 
    [PrimaryKey] 
    [JsonProperty(PropertyName = "id")] 
    public virtual int Id { get; set; } 

    [JsonIgnore] 
    public string Content { get; set; } 
} 

Così Entity Basta Id e Content. Le classi ereditarie potrebbero avere altre proprietà, ma sono interessato solo alla parte Content (JSON complesso).

È possibile accedere a tutti i tipi di entità diverse tramite Repository<T> generico. Devo conoscere lo Type della classe concreta perché le mappe T alle tabelle SQLite sottostanti tramite il provider di dati, create su ORM di SQLite-net.

Così, per esempio, se ho Schedule : Entity, allora sarei usando Repository<Schedule> per manipolare tabella denominata Schedule. Questa parte funziona bene.

Il problema principale è che "comandi" provengono dal client JavaScript, quindi ricevo richieste in formato JSON. In questo JSON ho una proprietà chiamata CollectionName che specifica la tabella desiderata (e il tipo concreto).

Quello che mi serve/voglio è un bel pezzo di codice & pulito che può recuperare le entità da qualsiasi tabella data. Così, il metodo seguito avrebbe dovuto risolvere tutti i miei problemi, ma scopre che non ha ...

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; 
} 

Quindi, se (sopra) Io uso il blocco .Result tutto funziona liek un fascino. Invece, se uso await, il codice potrebbe o potrebbe non funzionare. Sembra davvero dipendere dalle posizioni dei pianeti e/o dagli sbalzi d'umore di Flying Spaghetti Monster.

a caso, ma il più delle volte, data la linea sarà gettando

Impossibile eseguire il cast oggetto di tipo 'System.Runtime.CompilerServices.TaskAwaiter'1 [System.Collections.Generic.IEnumerable'1 [MyNamespace.Schedule]] ' per digitare' System.Runtime.CompilerServices.INotifyCompletion '.

Sto usando .NET 4.0 Extended Framework.

+0

Non era ben risposto laggiù - ma ancora - Sembra che si può evitare dinamico se il tipo di repository deriva da un non generico che può restituire un differito (abstract) GetAllAsync che Repository implementa. Quindi, invece di un repository dinamico, avresti un punto concreto da cui chiamare. – Clay

+0

Probabilmente non dovresti essere in attesa di un'espressione dinamica qui in primo luogo. Cosa c'è di sbagliato con 'await (Task >) repository.GetAllAsync()'? È meglio perché usa meno dinamica. – usr

+0

Non c'è 'T', solo' Tipo' risolto in fase di esecuzione. Non so davvero come farei a creare l'equivalente di '(Task >)' da 'typeof (Schedule)'. E 'T' non può essere semplicemente' Entity' (lancia l'eccezione). –

risposta

1

Esso può essere realizzato con 2 chiamate dinamiche:

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; 
} 

Modifica
Anche se quanto sopra dovrebbe funzionare, ci dovrebbe essere un disegno complessivo migliore. Purtroppo MS ha deciso di utilizzare le attività per l'asincronia, e dal momento che Task<TResult> è di classe, non possiamo trarre vantaggio dalla covarianza. Tuttavia, possiamo farlo con l'aiuto di una piccola estensione generica con il costo di una piccola spazzatura GC. Ma IMO semplifica enormemente tali progetti/implementazioni. Check it out:

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); 
     } 
    } 
} 
+0

Ma questo non sarebbe equivalente al mio codice di lavoro originale ma di blocco? –

+0

No - notare il task 'await;'. –

+0

Nella mia (inedita) domanda mi stava semplificando un po '. Il mio vincolo non era in realtà "Entità" ma "nuovo()". A causa di questo codice si stava rompendo a caso. Questo approccio funziona bene, anche senza la parte di modifica che hai aggiunto :) –

3

Se il tipo Repository<T> è un tipo creato dall'utente, è possibile basarlo su un tipo di base astratto con uno abstract Task<IEnumerable<Entity>> GetAllAsync(). Poi, dal momento che il repository a quanto pare ha già un metodo per la firma che - così si sta bene:

public abstract class Repository 
{ 
    public abstract Task<IEnumerable<Entity>> GetAllAsync(); 
} 

Poi abbiamo il tuo Repository<Entity> essere basata su Repository.

public class Repository<T>: Repository where T: Entity // Your existing class 
{ 
    public override async Task<IEnumerable<Entity>> GetAllAsync() 
    { 
    // Your existing implementation 
    } 
    //...existing stuff... 
} 

Poi, quando lo si utilizza, al posto di dinamica, si può dire:

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 
} 

Nessun dinamiche a tutti.

+0

Va bene. Basta che la tua classe sia 'Repository : Repository dove T: Entity'. Sto (non chiaramente) dicendo: due classi: 'Repository' e' Repository '. – Clay

+0

Questo funziona, grazie, con piccole modifiche. La dichiarazione del metodo astratto non può contenere la parola chiave 'async' e' GetAllEntitiesFrom' dovrebbe restituire 'attende repository.GetAllAsync()'. –

+0

Nella mia domanda (inedita) stavo semplificando un po '. Il mio vincolo non era in realtà "Entità" ma "nuovo()". A causa di questo codice si stava rompendo a caso. Grazie per il tuo impegno. Comunque ho deciso di usare l'approccio pubblicato da @Ivan Stoev perché è meno impegnativo per me (non c'è bisogno di aggiungere classi abtract/concrete). –

Problemi correlati