2015-11-26 7 views
5

Wiem, że to pytanie zostało już zadane, ale nie mogłem znaleźć odpowiedzi, która mnie usatysfakcjonuje. Co próbuję zrobić, to pobrać konkretny DbSet<T> w oparciu o jego typ nazwy.Znajdź ogólny DbSet w dynamicznym kontakcie DbContext

mam następujące:

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("MyDllAssemblyName")] 
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("MyCallingAssemblyName")] 

class MyDbContext : DbContext { 

    public DbSet<ModelA> A { get; set; } 
    public DbSet<ModelB> B { get; set; } 

    public dynamic GetByName_SwitchTest(string name) { 
     switch (name) { 
      case "A": return A; 
      case "B": return B; 
     } 
    } 

    public dynamic GetByName_ReflectionTest(string fullname) 
    { 
     Type targetType = Type.GetType(fullname); 
     var model = GetType() 
      .GetRuntimeProperties() 
      .Where(o => 
       o.PropertyType.IsGenericType && 
       o.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>) && 
       o.PropertyType.GenericTypeArguments.Contains(targetType)) 
      .FirstOrDefault(); 
     if (null != model) 
      return model.GetValue(this); 
     return null; 
    } 
} 

nie mam kłopot samego typu, czy jest za pomocą prostego przełącznika lub refleksji. Muszę jednak zwrócić typ jako dynamiczny, ponieważ nie wiem, jaki będzie typ DbSet. Potem gdzieś indziej w tym samym zespole, używam go w ten sposób:

// MyDbContext MyDbContextInstance.. 
var model = MyDbContextInstance.GetByName_SwitchTest("A"); 
var record1 = model.FirstOrDefault(); // It crashes here with RunTimeBinderException 

W tym momencie model zawiera instancję typu InternalDbSet<ModelA>. Stamtąd Jakiekolwiek użycie zrobić z obiektem model dostaję RunTimeBinderException: „Microsoft.Data.Entity.Internal.InternalDbSet” nie zawierają definicji „FirstOrDefault”

Śledczej w internecie, znalazłem blog post wyjaśniając, że (Dixit jego bloga):

powodem wywołanie FirstOrDefault() nie jest typem informacje o modelu nie jest dostępna w czasie wykonywania. Powodem, dla którego nie jest dostępny, jest fakt, że typy anonimowe nie są publiczne. Gdy metoda zwracająca instancję tego anonimowego typu zwraca numer System.Object odwołujący się do instancji typu anonimowego - typ , którego informacje nie są dostępne dla programu głównego.

Potem wskazuje, że rozwiązania:

Rozwiązanie jest całkiem proste. Wszystko co musimy zrobić, to otworzyć AssemplyInfo.cs projektu ClassLibrary1 i dodaj następującą linię do niego: [assembly:InternalsVisibleTo("assembly-name")]

Próbowałem to rozwiązanie w moim kodu, ale to nie działa. Dla informacji mam rozwiązanie asp.net 5 z dwoma zestawami uruchomionymi na Dnx dotnet46. Aplikacja i plik dll zawierający wszystkie moje modele i DbContext. Wszystkie odnośne wywołania, które robię, znajdują się w bibliotece DLL.

Czy to rozwiązanie ma szansę zadziałać? Czy czegoś brakuje? Jakieś wskazówki byłyby bardzo doceniane?

góry dzięki

[EDIT]

Próbowałem wrócić IQueryable<dynamic> zamiast dynamic i mogłem zrobić kwerendę podstawową model.FirstOrDefault();ale przede wszystkim chciałbym, aby móc filtrować na polu też:

var record = model.FirstOrDefault(item => item.MyProperty == true); 
+0

jak o zmianie dynamiczny do IEnumerable ? – Kelmen

+0

Zrobiłem coś podobnego (zmieniono na IQueryable ) i mimo że mogłem zrobić kilka zapytań takich jak 'model.FirstOrDefault()' to nie pozwoli mi na zrobienie rzeczy takich jak 'model.FirstOrDefault (item => item.MyProperty = = true) '. co zmniejsza użyteczność rzeczy. – DarkUrse

+0

Znalazłem ten sam problem. Czy masz szansę na rozwiązanie? – JosephGarrone

Odpowiedz

0

* Zastrzeżenie: Ta odpowiedź nie daje sensu stricto na moje pytanie. To raczej odmienne podejście do rozwiązania mojego własnego problemu.Zdaję sobie sprawę, że jest to konkretny przykład dla danej sytuacji, który nie zadziała dla wszystkich. Zamieszczam to podejście w nadziei, że pomoże to komuś, ale nie oznaczy tego jako odpowiedzi, ponieważ wciąż mam nadzieję na prawdziwe rozwiązanie.

Zacznijmy od tego, zaakceptujmy fakt, że jedynymi użytecznymi informacjami, jakie możemy uzyskać z bieżącego kodu, jest to, czy rekord istnieje, czy nie. Każda próba dynamicznych zapytań po tym wywołałaby wyjątek RuntimeBinderException.

Następnie kontynuujmy z innym faktem; DbContext.Add (obiekt) i DbContext.Update (obiekt) nie są oparte na szablonach, więc możemy ich użyć do zapisania naszych modeli (zamiast db.A.Add() lub db.A.Update())

In moja własna sytuacja, nie jest już konieczne, aby wypracować procedurę

  1. określenie modeli trochę inaczej

na początek muszę pole, które jest odzyskiwane we wszystkich moich modeli, które powinny oczywiście być sposób na zidentyfikowanie unikalnego rekordu.

// IModel give me a reliable common field to all my models (Fits my DB design maybe not yours though) 
interface IModel { Guid Id { get; set; } } 

// ModelA inherit IModel so that I always have access to an 'Id' 
class ModelA : IModel { 
    public Guid Id { get; set; } 
    public int OtherField { get; set; } 
} 

// ModelB inherit IModel so that I always have access to an 'Id' 
class ModelB : IModel { 
    public Guid Id { get; set; } 
    public string WhateverOtherField { get; set; } 
} 
  1. Re przeznaczenia dynamiczny odpytuje trochę coś zrobić nam prawidłowy

nie znalazłem sposób to zrobić inteligentną zapytanie dynamicznie, więc zamiast tego wiem, że mogę niezawodnie zidentyfikować rekord i wiedzieć, czy istnieje, czy nie.

class MyDbContext : DbContext { 

    public DbSet<ModelA> A { get; set; } 
    public DbSet<ModelB> B { get; set; } 

    // In my case, this method help me to know the next action I need to do 
    // The switch/case option is not pretty but might have better performance 
    // than Reflection. Anyhow, this is one's choice. 
    public bool HasRecord_SwitchTest(string name) { 
     switch (name) { 
      case "A": return A.AsNoTracking().Any(o => o.Id == id); 
      case "B": return B.AsNoTracking().Any(o => o.Id == id); 
     } 
     return false; 
    } 

    // In my case, this method help me to know the next action I need to do 
    public bool HasRecord_ReflectionTest(string fullname) 
    { 
     Type targetType = Type.GetType(fullname); 
     var model = GetType() 
      .GetRuntimeProperties() 
      .Where(o => 
       o.PropertyType.IsGenericType && 
       o.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>) && 
       o.PropertyType.GenericTypeArguments.Contains(targetType)) 
      .FirstOrDefault(); 
     if (null != model) 
      return (bool)model.GetValue(this).AsNoTracking().Any(o => o.Id == id); 
     return false; 
    } 

    // Update and save immediately - simplified for example 
    public async Task<bool> UpdateDynamic(object content) 
    { 
     EntityEntry entry = Update(content, GraphBehavior.SingleObject); 
     return 1 == await SaveChangesAsync(true); 
    } 

    // Insert and save immediately - simplified for example 
    public async Task<bool> InsertDynamic(object content) 
    { 
     EntityEntry entry = Add(content, GraphBehavior.SingleObject); 
     return 1 == await SaveChangesAsync(true); 
    } 
} 
  1. Trochę kanalizacji dać poczucie mojej sytuacji

Następnie, co muszę zrobić z tym dynamicznych zapytań była droga do replikacji dane z serwera do mojego klienta. (I pominięto znaczną część architektury uproszczenie tego przykładu)

class ReplicationItem 
{ 
    public ReplicationAction Action { get; set; } // = Create, Update, Delete 
    public string ModelName { get; set; } // Model name 
    public Guid Id { get; set; } // Unique identified across whole platform 
} 
  1. doprowadzania bitów.

A oto procedura, która łączy bity

public async void ProcessReplicationItem(ReplicationItem replicationItem) 
{ 
    using (var db = new MyDbContext()) 
    { 
     // Custom method that attempts to get remote value by Model Name and Id 
     // This is where I get the strongly typed object 
     var remoteRecord = await TryGetAsync(replicationItem.ModelName, replicationItem.Id); 
     bool hasRemoteRecord = remoteRecord.Content != null; 

     // Get to know if a local copy of this record exists. 
     bool hasLocalRecord = db.HasRecord_ReflectionTest(replicationItem.ModelName, replicationItem.Id); 

     // Ensure response is valid whether it is a successful get or error is meaningful (ie. NotFound) 
     if (remoteRecord.Success || remoteRecord.ResponseCode == System.Net.HttpStatusCode.NotFound) 
     { 
      switch (replicationItem.Action) 
      { 
       case ReplicationAction.Create: 
       { 
        if (hasRemoteRecord) 
        { 
         if (hasLocalRecord) 
          await db.UpdateDynamic(remoteRecord.Content); 
         else 
          await db.InsertDynamic(remoteRecord.Content); 
        } 
        // else - Do nothing 
        break; 
       } 
       case ReplicationAction.Update: 
        [etc...] 
      } 
     } 
    } 
} 

// Get record from server and with 'response.Content.ReadAsAsync' type it 
// already to the appropriately 
public static async Task<Response> TryGetAsync(ReplicationItem item) 
{ 
    if (string.IsNullOrWhiteSpace(item.ModelName)) 
    { 
     throw new ArgumentException("Missing a model name", nameof(item)); 
    } 

    if (item.Id == Guid.Empty) 
    { 
     throw new ArgumentException("Missing a primary key", nameof(item)); 
    } 

    // This black box, just extrapolate a uri based on model name and id 
    // typically "api/ModelA/{the-guid}" 
    string uri = GetPathFromMessage(item); 

    using (var client = new HttpClient()) 
    { 
     client.BaseAddress = new Uri("http://localhost:12345"); 

     HttpResponseMessage response = await client.GetAsync(uri); 
     if (response.IsSuccessStatusCode) 
     { 
      return new Response() 
      { 
       Content = await response.Content.ReadAsAsync(Type.GetType(item.ModelName)), 
       Success = true, 
       ResponseCode = response.StatusCode 
      }; 
     } 
     else 
     { 
      return new Response() 
      { 
       Success = false, 
       ResponseCode = response.StatusCode 
      }; 
     } 
    } 
} 

public class Response 
{ 
    public object Content { get; set; } 
    public bool Success { get; set; } 
    public HttpStatusCode ResponseCode { get; set; } 
} 

ps: nadal jestem zainteresowany prawdziwą odpowiedź, więc proszę zachować delegowania do drugiej odpowiedzi, jeśli masz prawdziwego dzielenia .

0

Więc jak to zrobiłem, gdy nie jestem świadomy <T> podczas kompilacji.

Najpierw trzeba uzyskać typ jako DbContext. Metoda zwraca nietypowe wystąpienie DbSet w celu uzyskania dostępu do encji danego typu w kontekście i magazynie bazowym.

public virtual DbSet Set(Type entityType) 

Uwaga tutaj argument jest typem jednostki, dla której zestaw powinien zostać zwrócony.I zestaw dla danego typu obiektu jest wartością zwracaną.

var type = Assembly.GetExecutingAssembly().GetTypes().FirstOrDefault(t => t.Name == <Pass your table name>); 

teraz raz mam ten typ

if(type != null) 
{ 
DbSet context = context.Set(type); 
} 

Albo jedna wkładka byłaby

DbSet mySet = context.Set(Type.GetType("<Your Entity Name>")); 
+1

Dziękuję za odpowiedź. Jednak - "publiczny wirtualny zestaw DbSet (Type entityType)" - wydaje się już nie istnieć z Entity Framework 7. Na dodatek 'DbSet' nie pozwoliłby mi na płynne zapytania API w modelach. – DarkUrse