2012-01-23 14 views
38

Sto utilizzando il framework di entità per connettersi al database. Ho un piccolo problema:Recupera un oggetto da entityframework senza campo ONE

Ho una tabella che ha una colonna varbinary (MAX) (con filestream).

Sto utilizzando la richiesta SQL per gestire la parte "Dati", ma EF per il resto (metadati del file).

Ho un codice che deve ottenere tutti i file id, filename, guid, data di modifica, ... di un file. Questo non ha bisogno del campo "Dati".

C'è un modo per recuperare un elenco ma senza questa colonna riempita?

Qualcosa di simile

context.Files.Where(f=>f.xyz).Exclude(f=>f.Data).ToList(); 

??

So che posso creare oggetti anonimi, ma ho bisogno di trasmettere il risultato a un metodo, quindi nessun metodo anonimo. E io non voglio metterlo in una lista di tipo anonimo, e quindi creare un elenco del mio tipo non anonimo (File).

L'obiettivo è di evitare questo:

using(RsSolutionsEntities context = new RsSolutionsEntities()) 
{ 
    var file = context.Files 
     .Where(f => f.Id == idFile) 
     .Select(f => new { 
      f.Id, f.MimeType, f.Size, f.FileName, f.DataType, 
      f.DateModification, f.FileId 
     }).FirstOrDefault(); 

    return new File() { 
     DataType = file.DataType, DateModification = file.DateModification, 
     FileId = file.FileId, FileName = file.FileName, Id = file.Id, 
     MimeType = file.MimeType, Size = file.Size 
    }; 
} 

(sto usando qui il tipo anonimo perché altrimenti si otterrà un NotSupportedException: l'entità o il tipo complesso 'ProjectName.File' non può essere costruita in un . query LINQ to Entities)

(ad esempio, questo codice generare l'eccezione precedente:

File file2 = context.Files.Where(f => f.Id == idFile) 
    .Select(f => new File() {Id = f.Id, DataType = f.DataType}).FirstOrDefault(); 

e "File" è il tipo che ottengo con un context.Files.ToList(). Questa è la buona classe:

using File = MyProjectNamespace.Common.Data.DataModel.File; 

File è una classe nota della mia EF DataContext:

public ObjectSet<File> Files 
{ 
    get { return _files ?? (_files = CreateObjectSet<File>("Files")); } 
} 
private ObjectSet<File> _files; 
+3

È sufficiente rimuovere la colonna dall'oggetto EF? – Gabe

+1

Vorrei poterlo ma è una colonna "NON NULL", e EF non mi piace quando ho una colonna non nulla che non è nel modello – J4N

+0

L'unica ragione per cui EF avrebbe un problema con le colonne non null escluse è durante 'INSERT' nel database. Puoi aggirare il problema usando procedure, trigger e altri metodi. Per 'SELECT' è assolutamente ** possibile ** escludere le colonne della tabella. – Yuck

risposta

14

C'è un modo per recuperare un elenco ma senza questa colonna riempita?

Non senza proiezione che si desidera evitare. Se la colonna è mappata, è una parte naturale della tua entità. L'entità senza questa colonna non è completa - si tratta di set di dati diversi = proiezione.

sto usando qui il tipo anonimo perché altrimenti si otterrà un NotSupportedException : l'entità o il tipo complesso 'ProjectName.File' non può essere costruita in un LINQ to Entities query.

Come dice l'eccezione, non è possibile proiettare sull'entità mappata. Ho menzionato la ragione sopra: la proiezione crea set di dati diversi e EF non ama le "entità parziali".

errore 16 Errore 3023: Problema in frammenti di mappatura partire dalla riga 2717: Colonna Files.Data nei file tabella deve essere mappato: Non ha valore impostazione predefinita e non è annullabile.

Non è sufficiente eliminare la proprietà dal progettista. È necessario aprire EDMX come XML ed eliminare la colonna da SSDL e questo renderà il modello molto fragile (ogni aggiornamento dal database riporterà la colonna). Se non si desidera mappare la colonna, è necessario utilizzare la vista del database senza la colonna e mappare la vista anziché la tabella, ma non sarà possibile inserire dati.

Come soluzione alternativa a tutti i problemi, utilizzare table splitting e separare la colonna binario problematica in un'altra entità con rapporto 1: 1 con l'entità principale File.

+8

Questo è un motivo per abbandonare EF. Assolutamente pazzo, questo è il pane e il burro di sempre, escludendo una singola colonna che si adatta perfettamente alle normali soluzioni di database come una singola colonna massima, mentre tutto il resto può essere utilizzato per elencare tali oggetti e così via.Le soluzioni nel modo attuale aumentano notevolmente la complessità, sia aggiungendo tutti i tipi di tipi extra per fare proiezioni, sia il codice per convertire esattamente lo stesso tipo allo stesso e viceversa; oppure, creando un tavolo 1: 1 e tutto ciò quando non è necessario. –

+0

Perché vogliamo evitare le proiezioni? È per motivi di prestazioni? – Gilles

+0

@Gilles: Siamo spiacenti, è troppo duro. Non volevo suggerire che la proiezione è cattiva. La proiezione è ottima se conosci le conseguenze. Di conseguenza, la proiezione non è un'entità: EF non fornirà alcuna delle sue funzionalità avanzate quando si restituisce la proiezione. Il caricamento di Eager potrebbe non funzionare (a seconda della query), il caricamento lento non funzionerà, il rilevamento delle modifiche non funzionerà, ecc. –

8

farei qualcosa di simile:

var result = from thing in dbContext.Things 
      select new Thing { 
       PropertyA = thing.PropertyA, 
       Another = thing.Another 
       // and so on, skipping the VarBinary(MAX) property 
      }; 

Dove Thing è il vostro soggetto che EF sa come materializzarsi. L'istruzione SQL risultante non dovrebbe includere la colonna grande nel suo set di risultati, poiché non è necessaria nella query.

EDIT: Dalle modifiche, si ottiene l'errore di NotSupportedException : l'entità o il tipo complesso 'ProjectName.File' non possono essere costruiti in query LINQ to Entities. perché non hai mappato quella classe come entità. È possibile che non sia includere oggetti in LINQ su query di entità di cui EF non è a conoscenza e si aspettano che generi istruzioni SQL appropriate.

È possibile mappare un altro tipo che esclude la colonna VarBinary(MAX) nella sua definizione o utilizzare il codice sopra.

+1

Ho già provato, ma EF mi dice che non posso inserire un tipo complesso nella selezione. – J4N

+0

Puoi pubblicare il codice che hai provato e l'errore che hai ricevuto nella tua domanda sopra? Sembra che tu stia cercando di utilizzare un oggetto di un tipo di cui EF non sa e non può generare istruzioni SQL. – Yuck

+0

Ho modificato il mio codice precedente – J4N

5

si può fare questo:

var files = dbContext.Database.SqlQuery<File>("select FileId, DataType, MimeType from Files"); 

o questo:

var files = objectContext.ExecuteStoreQuery<File>("select FileId, DataType, MimeType from Files"); 

a seconda della versione di EF

+0

se hai un dbcontext invece di un objectcontext puoi comunque accedere al contesto dell'oggetto. (dbContext as IObjectContextAdapter) .ObjectContext; C'è anche un metodo disponibile su dbcontext dbContext.Database.SqlQuery (stringa sql); –

+0

Questo ha funzionato per me. Tuttavia, si dovrebbe notare che ho aggiunto una colonna nulla per il campo dati vuoto. "ID SEL, Nome, Percorso, Crea Data, Ultimo aggiornamento, null AS Dati DA [File]"; – Rhyous

0

mi piacerebbe condividere i miei tentativi di risolvere questo problema nel caso in cui qualcun altro è nella stessa situazione.

Ho iniziato con quello suggerito da Jeremy Danyow, che per me è l'opzione meno dolorosa.

// You need to include all fields in the query, just make null the ones you don't want. 
var results = context.Database.SqlQuery<myEntity>("SELECT Field1, Field2, Field3, HugeField4 = NULL, Field5 FROM TableName"); 

Nel mio caso, avevo bisogno di un oggetto IQueryable<> risultato così ho aggiunto AsQueryable() alla fine. Questo naturalmente mi consente di aggiungere chiamate a .Where, .Take e agli altri comandi che tutti conosciamo e hanno funzionato correttamente. Ma c'è un avvertimento:

Il codice normale (in pratica context.myEntity.AsQueryable()) ha restituito un System.Data.Entity.DbSet<Data.DataModel.myEntity>, mentre questo approccio ha restituito System.Linq.EnumerableQuery<Data.DataModel.myEntity>.

Apparentemente questo significa che la mia query personalizzata viene eseguita "così com'è" non appena necessario e il filtro che ho aggiunto in seguito viene fatto in seguito e non nel database.

Pertanto, ho provato a imitare l'oggetto di Entity Framework utilizzando la query esatta che EF crea, anche con quegli alias [Extent1], ma non ha funzionato.Quando si analizza l'oggetto risultante, la sua interrogazione è conclusa come

FROM [dbo].[TableName] AS [Extent1].Where(c => ...

invece dei previsti

FROM [dbo].[TableName] AS [Extent1] WHERE ([Extent1]...

In ogni caso, questo funziona, e finché la tabella non è enorme, questo metodo essere abbastanza veloce Altrimenti non hai altra scelta che aggiungere manualmente le condizioni concatenando le stringhe, come il classico SQL dinamico. Un esempio molto semplice nel caso in cui non sai di cosa sto parlando:

string query = "SELECT Field1, Field2, Field3, HugeField4 = NULL, Field5 FROM TableName"; 
if (parameterId.HasValue) 
    query += " WHERE Field1 = " + parameterId.Value.ToString(); 
var results = context.Database.SqlQuery<myEntity>(query); 

Nel caso in cui il metodo a volte ha bisogno di questo campo, è possibile aggiungere un parametro bool e poi fare qualcosa di simile:

IQueryable<myEntity> results; 
if (excludeBigData) 
    results = context.Database.SqlQuery<myEntity>("SELECT Field1, Field2, Field3, HugeField4 = NULL, Field5 FROM TableName").AsQueryable(); 
else 
    results = context.myEntity.AsQueryable(); 

Se qualcuno riesce a far funzionare correttamente le estensioni Linq come se fosse l'oggetto EF originale, si prega di commentare in modo da poter aggiornare la risposta.

1

Avevo questo requisito perché ho un'entità Document che ha un campo Content con il contenuto del file, cioè circa 100 MB di dimensione, e ho una funzione di ricerca che volevo restituire il resto delle colonne.

ho scelto di utilizzare proiezione:

IQueryable<Document> results = dbContext.Documents.Include(o => o.UploadedBy).Select(o => new { 
    Content = (string)null, 
    ContentType = o.ContentType, 
    DocumentTypeId = o.DocumentTypeId, 
    FileName = o.FileName, 
    Id = o.Id, 
    // etc. even with related entities here like: 
    UploadedBy = o.UploadedBy 
}); 

Poi mio controller WebAPI passa questo oggetto results a una funzione paginazione comune, che applica una .Skip, .Take e .ToList.

Ciò significa che quando viene eseguita la query, non accedere alla colonna di Content, quindi i dati di 100 MB non viene toccato, e la query è veloce come ci si vuole/si aspetta che sia.

Successivamente, ho eseguito il cast della mia classe DTO, che in questo caso è praticamente uguale alla classe entity, quindi questo potrebbe non essere un passaggio da implementare, ma segue il tipico pattern di codifica WebApi , quindi:

var dtos = paginated.Select(o => new DocumentDTO 
{ 
    Content = o.Content, 
    ContentType = o.ContentType, 
    DocumentTypeId = o.DocumentTypeId, 
    FileName = o.FileName, 
    Id = o.Id, 
    UploadedBy = o.UploadedBy == null ? null : ModelFactory.Create(o.UploadedBy) 
}); 

Poi torno lista DTO:

return Ok(dtos); 

quindi utilizza la proiezione, che potrebbe non soddisfare i requisiti del manifesto originale, ma se si sta utilizzando le classi DTO, tu sei convertendo comunque. Si potrebbe altrettanto facilmente effettuare le seguenti operazioni a loro ritorno, i tuoi soggetti effettivi:

var dtos = paginated.Select(o => new Document 
{ 
    Content = o.Content, 
    ContentType = o.ContentType, 
    DocumentTypeId = o.DocumentTypeId, 
    //... 

A pochi passi in più, ma questo sta lavorando bene per me.

0

ho provato questo:

Dal diagramma edmx (EF 6), ho cliccato la colonna ho voluto nascondere a EF e sulle loro proprietà è possibile impostare la loro getter e setter a privati. In questo modo, per me funziona.

Restituisco alcuni dati che includono un riferimento Utente, quindi volevo nascondere il campo Password anche se è crittografato e salato, semplicemente non lo volevo sul mio json, e non volevo fare un:

Select(col => new {}) 

perché è un dolore da creare e mantenere, soprattutto per i tavoli grandi con molte relazioni.

Lo svantaggio di questo metodo è che se si rigenererà mai il modello, sarà necessario modificare nuovamente getter e setter.

0

sto usando qui il tipo anonimo perché altrimenti si otterrà un NotSupportedException : l'entità o il tipo complesso 'ProjectName.File' non può essere costruita in un LINQ to Entities query.

var file = context.Files 
     .Where(f => f.Id == idFile) 
     .FirstOrDefault() // You need to exeucte the query if you want to reuse the type 
     .Select(f => new { 
      f.Id, f.MimeType, f.Size, f.FileName, f.DataType, 
      f.DateModification, f.FileId 
     }).FirstOrDefault(); 

E anche la sua non una cattiva pratica di de-normalizzare la tabella in ulteriore, cioè uno con metadati e uno con payload per evitare la proiezione. La proiezione funzionerebbe, l'unico problema è che è necessario modificare ogni volta che una nuova colonna viene aggiunta alla tabella.

Problemi correlati