234 messaggi dal 08 marzo 2012
Ciao a tutti,

sto cercando di scrivere un query in Linq ma continuo a non capire come farla.
Ho un oggetto "MyObject" che ha una collezione di oggetti "Authorization" che mi serve per capire se un utente ha accesso a quell'oggetto e con quali privilegi.

Quando nel mio data access faccio una query voglio quindi verificare le permission ritornando solo i record ammessi per il tipo di operazione richiesta.

Diciamo che in SQL, supponendo di passare come parametri lo userId e l'objectId, dovrei fare una cosa del genere:

select * from myobject inner join authorization on myobject.id=authorization.objId where authorization.objId=3 and authorization.userId=6


Come faccio a scrivere questa query in Linq? Ci sto provando in tutti i modi ma non ci riesco.
Ovviamente myobject e authorization sono in una relazione 1:n nel senso che su un oggetto possono essere definite più autorizzazioni.

Avete idee a riguardo?

Grazie mille!
234 messaggi dal 08 marzo 2012
Sono riuscito a scrivere questa, ma non so se funziona esattamente come la query sopra descritta in quanto usa Any che non capisco esattamente a cosa corrisponda.

var objs = (from obj in ctx.MyObjs.Include("Authorization")
      where obj.Authorization.Any(a => a.ObjectGuid==obj.Guid && a.UserGuid==user.Guid) && obj.Status == status.ToString() 
      orderby obj.CreatedOn descending
      select obj);


Come faccio inoltre a sapere se una query Linq è scritta in modo da performare bene? In SQL ho pieno controllo di quello che scrivo e posso ottimizzare le query anche in base agli indici creati...in Linq come faccio a fare qualcosa di simile?
E' la prima volta che uso EF ed un ORM in generale...la prima sensazione è che non si controlla più nulla..Certo un bel risparmio di tempo (a parte quando bisogna sbattere la testa sul muro perchè non si capisce cosa sta facendo) ma mi sembra quasi di "dover incrociare le dita".

Grazie!
11.886 messaggi dal 09 febbraio 2002
Contributi
Ciao,


usa Any che non capisco esattamente a cosa corrisponda

Una premessa: non tutte le tecniche che usi in una query SQL hanno una corrispondenza tale-e-quale in Linq to Entities (e viceversa).
Linq to Entities è uno strumento che usi per interrogare il tuo modello concettuale che consiste di grafi di oggetti e che può avvalersi di ereditarietà, ovvero concetti che non esistono nel mondo relazionale.
Va appreso metodicamente e senza cercare di "tradurre" da query SQL esistenti, altrimenti rischi di trovarti facilmente in situazioni ingarbugliate come questa.

La query che hai scritto dovrebbe funzionare. Con l'uso di Any hai imposto che l'obj, per essere restituito come risultato, debba possedere un'autorizzazione per l'utente (e l'oggetto in questione).
Con una prima semplificazione diventerebbe così:
var objs = from obj in ctx.MyObjs
      where obj.Authorization.Any(a => a.UserGuid==user.Guid) && a.ObjectGuid==obj.Guid && obj.Status == status.ToString() 
      select obj;

Commenti:
  • L'Include non è necessario se non ti interessa che i dati delle autorizzazioni vengano restituite dalla query. (Se non le visualizzi a schermo, non ti serve recuperarle con l'Include)
  • Ho spostato il filtro su a.ObjectGuid fuori dall'Any. Infatti da obj.Authorization accedi già alle sole authorization di quell'oggetto
  • Ho tolto anche l'order by dato che *mi sembra* tu stia filtrando per la chiave primaria di obj, e perciò il risultato restituito sarà al massimo 1.


La query Linq to Entities si poteva scrivere anche in questo modo. Forse produrrà una query SQL più simile all'esempio che hai postato. Affinché funzioni, è necessario che tu abbia creato una proprietà di navigazione che ti porta da Authorization verso MyObj. Assumo anche che esista un DbSet<Atuhorization> chiamato Authorizations nel tuo DbContext.
var objs = from authorization in ctx.Authorizations
      where authorization.UserGuid==user.Guid && authorization.ObjectGuid==obj.Guid && authorization.MyObj.Status==status.ToString() 
      select authorization.MyObj;

La query qui sopra interroga il set delle Authorizations, filtrando per il guid di User e MyObj e alla fine restituisce MyObj.


Come faccio inoltre a sapere se una query Linq è scritta in modo da performare bene?

Entity Framework ti dà modo di tracciare tutte le query e i comandi inviati al database impostando un'Action alla proprietà Database.Log del DbContext.
using (var context = new TuoDbContext()) 
{ 
  context.Database.Log = testo => {
     //Scrivi il contenuto di testo da qualche parte.
     //Ad esempio con questo codice vedrai apparire le query
     //nel riquadro "Output" di Visual Studio
     System.Diagnostics.Debug.WriteLine(testo);
  };
  // qui il resto del codice
}

Leggi anche qui:
https://msdn.microsoft.com/en-us/data/dn469464.aspx#Log


In SQL ho pieno controllo di quello che scrivo e posso ottimizzare le query anche in base agli indici creati...in Linq come faccio a fare qualcosa di simile?

Anche con Entity Framework puoi decidere quali indici creare e su quali colonne. Vedi qui:
http://www.entityframeworktutorial.net/entityframework6/index-attribute-in-code-first.aspx
Esiste un modo di farlo anche da interfaccia fluente.
http://stackoverflow.com/questions/23892553/creating-unique-index-with-entity-framework-6-1-fluent-api#answers-header
Man mano che acquisisci esperienza con le query LINQ to Entities, imparerai naturalmente a scrivere query ottimizzate e non dovrai più controllare così spesso il log prodotto dal DbContext. Col tempo imparerai a fidarti di Entity Framework; nel frattempo è normale essere diffidenti, soprattutto se non hai mai usato un ORM prima d'ora.

mi sembra quasi di "dover incrociare le dita".

Accelera il tuo apprendimento procurandoti un libro. Ti consiglio in particolare quello di Julie Lerman su Code First.

ciao,
Moreno
Modificato da BrightSoul il 25 settembre 2016 13.24 -

Enjoy learning and just keep making
234 messaggi dal 08 marzo 2012
Ciao,

grazie delle risposte molto esaustive.
In effetti la "query" che avevo scritto funziona.

Due domande/osservazioni:
1) qual è il vantaggio di verificare a.Guid al di fuori di Any? Migliorano le performance?
2) la Orderby è necessaria in quanto tra Obj e Authorization esiste una relazione 1:n (dato che ovviamente sullo stesso oggetto potrei avere più autorizzazioni).

In realtà mi sarebbe piaciuto riuscire a creare un modello di autorizzazioni più generico...ti spiego.
Avevo creato una classe astratta AuthorizableGeneralObject, che eredita da GeneralObject (dove sono definiti sia Guid sia Id), che avevo relazionato 1:n con Authorization.

Avrei voluto fare in modo che per permettere ad un oggetto di essere acceduto solo previa autorizzazione bastasse che ereditasse da AuthorizableGeneralObject, con l'idea che scrivendo sulla tabella delle autorizzazioni guid oggetto e guid utente potesse verificare la corretta autorizzazione.
In realtà ho notato che EF andava a crearmi anche una "foreign key" per ogni oggetto diverso che ereditava da AuthorizableGeneralObject...creando una struttura che si sarebbe ovviamente riempita di null.
Non so se mi sono spiegato bene.

La struttura proposta da EF devo "accettarla" in quanto sto usando un ORM oppure ho sbagliato io a modellare la relazione tra le classi?
Scusa ma come dicevo sono abituato a mettere mano direttamente al db ed all'sql ed avrei creati una struttura su db con una logica ben diversa.

Sarebbe infine possibile che su tutte le query che fanno uso di oggetti che ereditano da AuthorizableGeneralObject sia applicata in automatico (ovvero senza doverla scrivere in ogni query, con anche il rischio di dimenticarsela) la verifica che l'utente sia autorizzato ad accedere a quell'oggetto?
Ho visto che c'è l'evento MaterializedObject ma non so se può fare al mio caso o se impatta negativamente sulle performance.

Grazie e scusa il poema!
234 messaggi dal 08 marzo 2012
Nessuna idea in merito?

Grazie e ciao
11.886 messaggi dal 09 febbraio 2002
Contributi
Ciao,


1) qual è il vantaggio di verificare a.Guid al di fuori di Any? Migliorano le performance?

Non sono sicuro, prova a vedere se la query SQL cambia. A naso mi sembrava più opportuno metterlo fuori ma preferirei usare la seconda riscrittura della query che non causa subqueries SQL.


2) la Orderby è necessaria in quanto tra Obj e Authorization esiste una relazione 1:n (dato che ovviamente sullo stesso oggetto potrei avere più autorizzazioni).

Penso che l'order by non sia necessario perché tu stai selezionado 1 Obj, dato che filtri per il suo Guid, che immagino sia la chiave primaria. Quindi, dato che il risultato della query sarà solo 1, non c'è motivo di ordinare i vari Obj per la data di creazione.


EF andava a crearmi anche una "foreign key" per ogni oggetto diverso che ereditava da AuthorizableGeneralObject

Volendo puoi invertire la relazione: è l'oggetto che possiede una foreign key verso Authorization, che quindi diventa l'entità principale.


La struttura proposta da EF devo "accettarla" in quanto sto usando un ORM oppure ho sbagliato io a modellare la relazione tra le classi?

EF non "propone" una struttura ma crea uno schema relazionale esattamente corrispondente alle relazioni che hai mappato da codice. Immagino che tu debba trovare un modo alternativo di modellare le relazioni. Potresti provare l'inversione di cui parlavo qui sopra.


sia applicata in automatico (ovvero senza doverla scrivere in ogni query, con anche il rischio di dimenticarsela) la verifica che l'utente sia autorizzato ad accedere a quell'oggetto?

Sì, anziché esporre le collezioni di entità tramite DbSet, potresti esporle attraverso delle proprietà di tipo IQueryable (o un metodo generico) che restituiscono già la collezione filtrata.
Sarà ancor più efficace se impedisci al resto della tua applicazione di "vedere" il DbContext. Puoi nascondere la sua implementazione dietro un'interfaccia del genere:
public interface IRepositoryObj {
  IQueryable<Obj> Objs {get;}
}

Che viene implementata così dal DbContext
public class TuoContesto : DbContext, IRepositoryObj {
  public IQueryable Objs {
    get{
       return Set<Obj>().Where(criterioDiFiltro);
    }
  }
}


Dovre criterioDiFiltro è una lambda che tiene conto dell'identità dell'utente e impone un filtro che esclude gli Obj privi di autorizzazione.
In questo modo, quando invii query LINQ a partire da Objs in vari punti della tua applicazione, non potrai sbagliarti perché la lista è stata filtrata alla fonte.

Questo era un esempio per dimostrare il concetto e che non tiene conto delle ultime considerazioni (invertire la relazione ed evitare subqueries).

ciao,
Moreno

Enjoy learning and just keep making

Torna al forum | Feed RSS

ASPItalia.com non è responsabile per il contenuto dei messaggi presenti su questo servizio, non avendo nessun controllo sui messaggi postati nei propri forum, che rappresentano l'espressione del pensiero degli autori.