2015-08-11 54 views
5

Restated mia interrogazione, vecchio Testo sottonon possono accedere ai dati correlati dopo lo smaltimento di DataContext in LINQ to SQL

Come sto ancora sperando in una risposta vorrei ribadire la mia domanda. Immagine Ho una GUI con due elenchi, uno che mostra un elenco di tutte le voci di un database tblOrders e un altro che mostra gli elementi in ciascun ordine.

posso usare Linq2Sql o EF per ottenere tutti gli ordini dal database, in questo modo:

using (DataClasses1DataContext DataContext = new DataClasses1DataContext()) 
    ListOfOrders = DataContext.tblOrder.ToList(); 

posso visualizzare questi ordine in un elenco o datagridview. Quindi, su una selezione di uno dei lavori, desidero accedere alla raccolta di entità dalla tabella tblItems. Posso fare questo in questo modo:

ListOfOrders.First().tblItems.toList(); 

meno che non possa, perché ho bisogno del DataContext che sia stata ceduta. Questo ha senso in un modo, dal momento che non posso garantire che non ci siano nuovi elementi che sono stati aggiunti a quell'ordine da quando ho recuperato la collezione ListOfOrders. So ideally I would like to check if there has been an addition to a tblOrder.tblItems e, se necessario, ho recuperato la nuova raccolta dal server.

Lo sfondo è che il mio modello è un po 'più complesso: consiste in ordini, composti da parti, che consistono in attività. Quindi per valutare lo stato di ogni ordine devo recuperare tutte le parti che appartengono a un ordine e per ognuna di esse devo vedere quante delle attività sono state completate. In un database con 200 lavori, ognuno con 1 a 10 parti, ciò semplifica il mio programma in termini di reattività ...

Qualcuno può aiutarmi?

domanda iniziale

ho trovato un sacco di domande riguardanti DataContext, ma non ho ancora trovato una soluzione al mio problema. Se faccio la seguente:

using (DataClasses1DataContext DataContext = new DataClasses1DataContext()) 
    ListOfOrders = DataContext.tblOrder.ToList(); 

Questo mi dà un elenco delle entità nella tabella tblOrder. ma ora voglio fare questo:

DataTable Orders = new DataTable(); 
Orders.Columns.Add("Order-ID", typeof(int)); 
Orders.Columns.Add("Order-Name", typeof(string)); 
Orders.Columns.Add("Order-Items", typeof(string)); 

dataGridView1.DataSource = Orders; 

foreach (tblOrder Order in ListOfOrders) 
{ 
    var newRow = Orders.NewRow(); 
    newRow["Order-ID"] = Order.orderID; 
    newRow["Order-Name"] = Order.orderName; 
    newRow["Order-Items"] = string.Join(", ", Order.tblItem.Select(item=> item.itemName).ToList()); 
     // System.ObjectDisposedException 
    (dataGridView1.DataSource as DataTable).Rows.Add(newRow); 
} 

E non posso perché l'accesso a tutte le entità nella tblItem tavolo che sono legati agli ordini di chiave esterna non sembrano essere memorizzati.

Quello che sta lavorando:

DataClasses1DataContext DataContext = new DataClasses1DataContext(); 
ListOfOrders = DataContext.tblOrder.ToList(); 

DataTable Orders = new DataTable(); 
Orders.Columns.Add("Order-ID", typeof(int)); 
Orders.Columns.Add("Order-Name", typeof(string)); 
Orders.Columns.Add("Order-Items", typeof(string)); 

dataGridView1.DataSource = Orders; 

foreach (tblOrder Order in ListOfOrders) 
{ 
    var newRow = Orders.NewRow(); 
    newRow["Order-ID"] = Order.orderID; 
    newRow["Order-Name"] = Order.orderName; 
    newRow["Order-Items"] = string.Join(", ", Order.tblItem.Select(item=> item.itemName).ToList()); 
    (dataGridView1.DataSource as DataTable).Rows.Add(newRow); 
} 

DataContext.Dispose(); 

ma se ho capito questo non è desiderabile.

EDIT

ho esteso il mio esempio in un Controller-View-pattern.

using System.Collections.Generic; 
using System.Linq; 

namespace TestApplication 
{ 
    class Controller 
    { 
     private List<tblOrder> _orders; 
     public IList<tblOrder> Orders 
     { 
      get 
      { 
       return _orders; 
      } 
     } 

     public Controller() 
     { 
      using (var DataContext = new DataClasses1DataContext()) 
      { 
       _orders = DataContext.tblOrder.ToList(); 
      } 
     } 
    } 
} 

E la vista ora recupera gli Ordini dal controller:

using System.Data; 
using System.Linq; 
using System.Windows.Forms; 

namespace TestApplication 
{ 
    public partial class Form1 : Form 
    { 
     public Form1() 
     { 
      InitializeComponent(); 

      Controller controller = new Controller(); 

      DataTable Orders = new DataTable(); 
      Orders.Columns.Add("Order-ID", typeof(int)); 
      Orders.Columns.Add("Order-Name", typeof(string)); 
      Orders.Columns.Add("Order-Items", typeof(string)); 

      dataGridView1.DataSource = Orders; 

      foreach (tblOrder Order in controller.Orders) 
      { 
       var newRow = Orders.NewRow(); 
       newRow["Order-ID"] = Order.orderID; 
       newRow["Order-Name"] = Order.orderName; 
       newRow["Order-Items"] = string.Join(", ", Order.tblItem.Select(item=> item.itemName).ToList()); 
       (dataGridView1.DataSource as DataTable).Rows.Add(newRow); 
      } 
     } 
    } 
} 

Purtroppo il problema rimane lo stesso ...

+0

Non è consigliabile chiamare Dispose manualmente anziché utilizzare un blocco 'using'. Ma potresti risolverlo banalmente. Basta inserire un blocco usando. – nvoigt

+0

Questo è corretto e funziona per questo esempio, ma questo diventa un problema se, ad esempio, ho un controller che presenta una lista di ordini a cui si accede da una vista, che visualizza tale lista. – Jonidas

+0

Non si deve disporre il contesto troppo rapidamente, ma è possibile mantenere il contesto come membro di istanza del modulo e disporre del contesto quando il modulo viene eliminato. –

risposta

4

Entity Framework lazy-carica dati oggetto, ovvero carica la quantità minima di dati che deve il più tardi possibile. Prendete la vostra query:

ListOfOrders = context.tblOrder.ToList(); 

Qui si richiede tutti i record nella tabella tblOrder. Entity Framework non legge nel tuo programma e capisci che vedrai la tabella tblItem dopo che il contesto è stato eliminato, quindi presume che possa caricare i dati tblItem in seguito. Essendo pigro, carica il minimo richiesto: l'elenco dei record in tblOrder.

Ci sono due modi è un modo per aggirare questo:

Disable caricamento lazy

using (var context = new DataClasses1DataContext()) 
    { 
     data.Configuration.LazyLoadingEnabled = false; 
     _orders = context.tblOrder.ToList(); 
    } 

Con LazyLoadingEnabled=false Entity Framework selezionerà l'intero contenuto della tabella tblOrder e tutte le tabelle ad esso connessi tramite una chiave esterna. Questo potrebbe richiedere del tempo e utilizzare molta memoria, a seconda delle dimensioni e del numero di tabelle correlate.

(Edit:.. Il mio errore, disabling LazyLoading does not enable eager loading e there is no default configuration for eager loading Scuse per la disinformazione Il comando .Include seguito sembra che l'unico modo per andare.)

include ulteriori tabelle

using (var context = new DataClasses1DataContext()) 
    { 
     data.Configuration.LazyLoadingEnabled = false; 
     _orders = context.tblOrder.Include("tblItems").ToList(); 
    } 

Questo indica a Entity Framework di caricare tutti i dati relativi da tblItems in primo piano mentre carica i dati della tabella tblOrders. EF non carica ancora alcun dato da altre tabelle correlate, in modo che altri dati non siano disponibili dopo che il contesto è stato eliminato.

Tuttavia, questo non risolve il problema dei dati non aggiornati, ovvero, nel tempo, i record in dataGridView1 non saranno più aggiornati. Potresti avere un pulsante o un timer che attiva un aggiornamento. Il metodo di aggiornamento più semplice sarebbe quello di eseguire di nuovo l'intero processo - ricarica _orders, quindi ripopolare in modo selettivo dataGridView1.

+1

nel primo contesto di esempio verrà smaltito – Backs

+0

@Backs Sì, è l'idea. Il contesto sarà correttamente disposto e i dati saranno presenti nella lista degli oggetti. – MikeTV

+0

Include era in realtà ciò che ha funzionato per me. Grazie! :) – Jonidas

1

I consigli di creare nuova classe risultato, che contiene i dati:

class Result 
{ 
    int ID {get;set;} 
    string OrderName {get;set;} 
    IEnumerable<string> ItemNames {get;set;} 
} 

Seleziona bisogno di dati:

class Controller 
{ 
    private List<Result> _orders; 
    public IList<Result> Orders 
    { 
     get 
     { 
      return _orders; 
     } 
    } 

    public Controller() 
    { 
     using (var DataContext = new DataClasses1DataContext()) 
     { 
      _orders = DataContext.tblOrder.Select(o=> 
         new Result 
         { 
          ID = o.orderID, 
          OrderName = o.orderName, 
          ItemNames = o.tblItem.Select(item=> item.itemName) 
         }).ToList(); 
     } 
    } 
} 

E dopo che legano questa collezione mi vedono griglia. Quindi, avrai tutti i tuoi dati prima di eliminare il contesto e non avrai più dipendenze.

0

Solo una risposta alla prima domanda.
Come EF dice, hai eliminato il contesto dopo [unità di] lavoro (un must in ASP, una buona pratica in WinForm/WPF).

using (DataClasses1DataContext DataContext = new DataClasses1DataContext()) 
    ListOfOrders = DataContext.tblOrder.ToList(); 

dopo questo, se si tenta di eseguire questa dichiarazione

ListOfOrders.First().tblItems.toList(); 

EF funziona in questo modo:
- ottenere il primo elemento di ListOfOrders che è un proxy per l'oggetto.
- prova ad ottenere i tblItem relativi a LazyLoad.
A questo punto, per recuperare i tblItems è necessario un accesso al database con un contesto che viene recuperato dal proxy del primo elemento ListOfOrder MA il contesto è eliminato.

Quindi non è possibile utilizzare questo approccio.

Ci sono 2 soluzioni e alcune variazioni su di loro:
- E 'possibile leggere tutte le tblItems prima di smaltire il contesto (lo si può vedere in un'altra risposta), ma è un approccio non scalabile.
- Quando è necessario accedere a tblItems, recuperare l'ordine da un nuovo contesto e fare ciò che è necessario prima di smaltirlo. Nel tuo caso

using (DataClasses1DataContext DataContext = new DataClasses1DataContext()) 
{ 
    int Id = ListOfOrders.First().Id; 
    var myListOftblItems = DataContext.tblOrder.Find(Id).tblItems.ToList(); 
} 

Una variante di questo approccio è quello di leggere solo tblItems. È possibile farlo se tblItems ha esposto anche il campo chiave esterna e non solo la proprietà di navigazione (di solito non lo faccio).

using (DataClasses1DataContext DataContext = new DataClasses1DataContext()) 
{ 
    int Id = ListOfOrders.First().Id; 
    var myListOftblItems = DataContext.tblItems.Where(t => t.IdOrder == Id).ToList(); 
} 
0

Il comportamento predefinito di EF è per le entità Lazy Load. In generale questa è una buona idea e penso che non dovresti disabilitarla se non hai ottime ragioni.

EF fornisce anche metodi per Eager Loading entità specificate:

// Add this! 
using System.Data.Entity; 

quindi è possibile utilizzare il metodo Include:

public static IList<Order> GetOrdersAndItems() 
{ 
    List<Order> orders; 

    using (var context = new ShopDC()) 
    { 
     context.Database.Log = Console.WriteLine; 

     Console.WriteLine("Orders: " + context.Orders.Count()); 

     orders = context.Orders 
      .Where(o => o.OrderID > 0) 
      // Tells EF to eager load all the items 
      .Include(o => o.Items) 
      .ToList(); 
    } 

    return orders; 
} 

Ora è possibile utilizzare GetOrdersAndItems per caricare un elenco con tutti i vostri ordini e ogni ordine conterrà tutti gli articoli:

public static void Run() 
{ 
    IList<Order> disconnectedOrders = GetOrdersAndItems(); 

    foreach (var order in disconnectedOrders) 
    { 
     Console.WriteLine(order.Name); 

     foreach (var item in order.Items) 
     { 
      Console.WriteLine("--" + item.Name); 
     } 
    } 
} 

Per ulteriori esempi e più livelli include un'occhiata here

Nota: utilizzando un assistente o di un controller o un repository non è importante nella comprensione di questo meccanismo, così ho semplicemente usato un metodo statico.