11.886 messaggi dal 09 febbraio 2002
Contributi
ciao,

doppiomango ha scritto:

Ad ogni modo mi era sembrato di capire che fosse il campo ordinamento a gestire l'ordinamento gerarchico dei risultati

E' così, anche se - a scanso di equivoci - il campo 'ordinamento' non deve trovarsi nella tabella ma è creato al volo dalla query, concatenando gli ID delle categorie. Per pura curiosità: ciò che si ottiene è una notazione simile a quella usata nella classificazione Dewey. Come vedi lì, le categorie con ID più basso hanno degli zeri alla loro sinistra, affinché tutti i numeri siano sempre composti da 3 cifre, in modo che anche l'ordinamento alfabetico possa funzionare in maniera consistente. Una cosa analoga succede con il casting a Binary.

doppiomango ha scritto:

basterà aggiungere il campo dopo ordinamento nell'order by, giusto?

Purtroppo no perché, essendo 'ordinamento' una composizione dei campi ID, saranno i valori degli ID a determinare l'ordine di apparizione all'interno dello stesso livello. Aggiungere il campo nell'ORDER BY non produrrà effetto perché due categorie nello stesso ramo sono già state discriminate dai loro valori di 'ordinamento', che sono diversi.
Quindi devi andare a modificare la logica con cui il valore di 'ordinamento' viene creato. La soluzione è comunque facile perché basta sostituire l'ID con un campo Priorità. Ammettiamo che tu l'abbia creato come campo Tinyint (0-255 è un range sufficiente) dove le categorie con i valori più bassi appaiono in alto nella lista. Modifica così la logica di creazione dell'ordinamento, coinvolgi solo il campo Priorità.
CAST(cte.ordinamento + CAST(cat.Priorità AS BINARY(1)) AS VARBINARY(900))


doppiomango ha scritto:

I risultati sono sempre gli stessi...

mmh, non riesco a riprodurre il problema, da me sta funzionando. Prova ad eseguire questo script in un db di test. Lo script creerà la tabella, aggiungerà dei dati ed eseguirà la query.
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[Categorie](
  [Id] [int] IDENTITY(1,1) NOT NULL,
  [IdPadre] [int] NULL,
  [Nome] [nvarchar](50) NOT NULL,
  [Percorso] [nvarchar](50) NOT NULL,
  [Priorità] [tinyint] NOT NULL,
 CONSTRAINT [PK_Categorie] PRIMARY KEY CLUSTERED 
(
  [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

SET IDENTITY_INSERT [dbo].[Categorie] ON
GO

INSERT INTO [dbo].[Categorie] (Id, IdPadre, Nome, Percorso, Priorità) VALUES
(1, NULL, 'Home', '/', 0),
(2, 1, 'Prodotti', '/prodotti', 10),
(3, 1, 'Azienda', '/azienda', 5),
(4, 3, 'Chi siamo', '/azienda/chi-siamo', 20),
(5, 3, 'Dove siamo', '/azienda/dove-siamo', 50),
(6, 2, 'Sedie', '/prodotti/sedie', 50),
(7, 2, 'Divani', '/prodotti/carrelli', 20),
(9, 6, 'Legno', '/prodotti/sedie/legno', 10),
(10, 6, 'Rattan', '/prodotti/sedie/rattan', 20),
(11, 7, 'Pelle', '/prodotti/divani/pelle', 20),
(12, 7, 'Tessuto', '/prodotti/divani/tessuto', 50),
(13, 7, 'Ecopelle', '/prodotti/diviani/ecopelle', 10);
GO

WITH cteCategorie AS
(
SELECT Id, IdPadre, Nome, 0 as Livello, Percorso, Priorità, CAST(Priorità AS VARBINARY(900)) AS Ordinamento
FROM Categorie
WHERE (IdPadre IS NULL)

UNION ALL

SELECT cat.Id, cat.IdPadre, cat.Nome, Livello + 1, cat.Percorso, cat.Priorità, CAST(cte.ordinamento + CAST(cat.Priorità AS BINARY(1)) AS VARBINARY(900))
FROM Categorie AS cat INNER JOIN cteCategorie AS cte ON cte.Id = cat.IdPadre
)
SELECT Id, IdPadre, Nome, Percorso, Priorità, Livello FROM cteCategorie ORDER BY Ordinamento;


doppiomango ha scritto:

Ho già implementato sia il menù custom che il codice che mi hai fornito, adeguatamente modificato, con successo =)

benissimo :) manca solo la questione della query allora.
ciao
Modificato da BrightSoul il 13 novembre 2012 22.14 -

Enjoy learning and just keep making
161 messaggi dal 07 settembre 2009
Problema trovato!
vedendo la dichiarazione dei campi della tabella di test ho notato che il campo ID è dichiarato diversamente, io usavo il tipo numeric invece di int, modificando il tipo ora ottengo i dati nell'ordine voluto, sostituendo poi il campo id con priorità ottengo ancora l'ordinamento voluto.

A questo punto mi chiedo se convenga mantenere numeric quando dichiaro dei campi primari o se è meglio usare il campo int.

Come sempre grazie di tutto (lo ripeto ad ogni risposta ma è sempre un ringraziamento sincero).


Davide


Edit:
Ora mi trovo davanti il seguente quesito:
ho una query che mi stampa i dati in ordine gerarchico, ma per il databinding ho bisogno comunque di una funzione ricorsiva come la tua, altrimenti non riconosce il padre dai figli (ovviamente)... a questo punto immagino che i dati della query li debba mettere sempre in un datatable, ma poi come faccio a dirgli quale riga deve andare a prendere senza usare il comando Select? Penso (e spero, più per te, che ti sei preso la briga di aiutarmi in questo "arduissimo" compito, che per me) sia l'ultimo ostacolo, anche se potrei riprendere il codice della tua pagina default.aspx.cs così com'è e andare avanti ma poi avremmo perso tutto questo tempo dietro le cte per nulla...
Modificato da doppiomango il 14 novembre 2012 11.13 -

Un'ultima questione che riguarda il template del menu ricorsivo...
Il codice del link che mi hai mandato accetta un solo campo Text che contiene il dato da stampare nel template... è chiaro capire come sia possibile aggiungere nuovi campi predefiniti come l'ID... ma prendendo ad esempio un listview, se effettuo il databind con una classe che contiene 5 campi, mi è possibile stampare tutti e 5 i campi semplicemente con il comando <% Eval(nomevariabile) %>... com'è possibile farlo con il template custom senza dover ogni volta effettuare un override del costruttore con i campi di interesse? Non è particolarmente importante per ora ma abbiamo fatto 30 facciamo 31
Modificato da doppiomango il 14 novembre 2012 13.16 -
11.886 messaggi dal 09 febbraio 2002
Contributi
ciao, prego :)

doppiomango ha scritto:

A questo punto mi chiedo se convenga mantenere numeric quando dichiaro dei campi primari o se è meglio usare il campo int.

Vai con int quando vuoi creare delle chiavi primarie autoincrementanti. Il campo di tipo numeric si presta di più quando vuoi trattare con numeri decimali, ad esempio quando devi trattare dei prezzi.
Il numeric è un tipo di dato che occupa dai 5 bytes in poi, mentre int solo 4 (o meno se scegli i suoi "fratelli minori" come smallint o tinyint). Il motivo per cui la tua query dava risultati inaspettati era perché, probabilmente, un di tipo di dato di 5 bytes veniva fatto il casting a BINARY(4). L'ultimo byte si perdeva per strada, penso.

A proposito dell'ordinamento, tutto sommato, è meglio reintrodurre l'ID insieme al campo Priorità. Il motivo è che, se due voci nello stesso livello hanno inavvertitamente lo stesso valore di Priorità, le loro sottovoci potrebbero mischiarsi. E' sempre bene che il valore di orginamento sia univoco. Quindi - come soluzione definitiva - prova questa:
CAST(cte.ordinamento + CAST(cat.Priorità AS BINARY(1)) + CAST(cat.Id AS BINARY(4)) AS VARBINARY(900))


doppiomango ha scritto:

ho una query che mi stampa i dati in ordine gerarchico, ma per il databinding ho bisogno comunque di una funzione ricorsiva come la tua

eh no altrimenti il lavoro fatto dalla Common Table Expression sarebbe stato inutile. Guarda qualche post fa, il codice che sfrutta la classe Stack. Grazie a quella ti basta fare un ciclo for sui risultati, e non più una funzione ricorsiva.

doppiomango ha scritto:

mi è possibile stampare tutti e 5 i campi semplicemente con il comando <% Eval(nomevariabile) %>... com'è possibile farlo con il template custom senza dover ogni volta effettuare un override del costruttore con i campi di interesse?

Puoi costruirti un templated control, derivando da HierarchicalDataBoundControl. Il mio primo post di questa discussione conteneva alcuni link. Ad esempio questo:
A custom, templated HierarchicalDataBoundControl
In quell'esempio l'autore crea un controllo personalizzato che dispone di un suo <ItemTemplate> che gli permette di usare le espressioni di binding tipo <%# Eval("nomecampo") %>

ciao!

Enjoy learning and just keep making
161 messaggi dal 07 settembre 2009
Lo stack mi era sfuggito quando cercavo la soluzione nelle risposte precedenti, essendo in mezzo a una discussione non l'avevo notata anche se mi ricordavo che era un problema già trattato...
Ora ho risolto ma ho dovuto modificare
else if (livelloCorrente < stack.Count)
{
  stack.Pop();
  collezioneCorrente = stack.Peek().Sottopagine;
} 

con

else if (livelloCorrente < stack.Count)
{
  while (livelloCorrente < stack.Count)
    stack.Pop();
  collezioneCorrente = stack.Count == 0 ? collezione : stack.Peek().Sottopagine;
} 
altrimenti nel caso ci siano più radici va in errore poichè si ritrova una stack vuota, e nel caso si trovasse una differenza di più livelli (es si passa da un livello 4 a un livello 2) elimina il giusto numero di elementi dalla stack, altrimenti graficamente ottenevo un errore... questo lo dico nel caso qualcuno, in futuro, dovesse trovare interessante l'argomento trattato =)


Tuttavia non riesco a capire, per quanti sforzi abbia fatto e quanti neuroni mi sia giocato, cosa restituisce stack.Peek().Sottopagine...
o meglio, stack.Peek() mostra il primo elemento (l'ultimo inserito) senza toglierlo dalla stack, e fin qui ci siamo, ma .SottoPagine (al di là quindi della classe che lo richiama, cosa tira fuori? mi servirebbe un esempio pratico perchè quando cercavo di scrivere tutti i dati e i risultati dei vari cicli su SottoPagine ho messo un punto interrogativo fidandomi dei calcoli fatti, ma è qualcosa che non posso e non voglio tralasciare... In breve cosa contiene SottoPagine alla fine? non può contenere esattamente le sottopagine visto che non le ho ancora ciclate...


Per quanto riguarda <% Eval(nomevariabile) %> stavo già usando quell'esempio per creare il template, ma la classe a cui è associata restituisce solo Text e devo andare a modificare il costruttore aggiungendo x variabili per ogni tipologia di menu... io invece vorrei che prendesse in automatico il numero di variabili della classe da cui effettua il databind, in questo caso collezionedipagine che a sua volta prende i dati dalla classe pagina.

Rivendendo un pò il codice ho capito che la chiave di tutto sono le 2 interfacce INamingContainer e IDataItemContainer... in questo caso è sufficiente implementare le interfacce alla classe collezionedipagine?


Sempre grazie per la pazienza =)

Davide
11.886 messaggi dal 09 febbraio 2002
Contributi
ciao,

doppiomango ha scritto:

nel caso ci siano più radici va in errore poichè si ritrova una stack vuota, e nel caso si trovasse una differenza di più livelli (es si passa da un livello 4 a un livello 2) elimina il giusto numero di elementi dalla stack, altrimenti graficamente ottenevo un errore..

bravissimo, hai avuto buon occhio, è esattamente così come hai descritto. Grazie per la segnalazione, vado a correggere il problema per evitare di diffondere codice buggato :)

doppiomango ha scritto:

SottoPagine (al di là quindi della classe che lo richiama, cosa tira fuori? Non può contenere esattamente le sottopagine visto che non le ho ancora ciclate...

Sì, è proprio la collezione delle sottopagine. Inizialmente la collezione Sottopagine sarà vuota ma andrai a popolarla a mano mano che il ciclo for va avanti.
L'obiettivo, a fine ciclo, è aver aggiunto tutte le pagine nelle collezioni dei rispettivi genitori.
Ecco la linea di codice che aggiunge la pagina dell'iterazione corrente alla collezione del suo genitore.
//collezioneCorrente identifica le sottopagine del genitore di questa pagina
//Il genitore l'avevamo selezionato nel blocco if...elseif...else
ultimaPagina = collezioneCorrente.Aggiungi(riga["Titolo"].ToString(), riga["Percorso"].ToString());


doppiomango ha scritto:

ma la classe a cui è associata restituisce solo Text e devo andare a modificare il costruttore aggiungendo x variabili per ogni tipologia di menu...

Uhm, Eval dovrebbe funzionare indipendentemente dalle proprietà esposte dal MyDataItem. Hai provato e ti dà un errore? Ad ogni modo bisogna che il MyDataItem sia slegato dal tipo di dato (Pagina, nel nostro caso) con cui si trova a lavorare.
Gli creerei un costruttore semplice, che accetti un solo parametro object. Istanziando MyDataItem, gli passeremo l'oggetto corrente senza curarci che sia una Pagina o altro tipo.
public class MyDataItem : Control, INamingContainer, IDataItemContainer
{
        //nel costruttore mi limito a raccogliere l'oggetto e ad impostarlo sulla proprietà pubblica DataItem
        public MyDataItem(object dataItem)
        {
            DataItem=dataItem;
        }
        // Ecco la proprietà pubblica.
        // La forniamo nel caso voglia interagire in qualche modo con l'oggetto (es. invocarne un metodo)
        public object DataItem {get; private set;}

        //Le restanti proprietà servono giusto per l'implementazione di IDataItemContainer
        public int DataItemIndex { get { return 0; } }
        public int DisplayIndex { get { return 0; } }
}


Ora, quando è il momento di istanziare MyDataItem, passagli l'oggetto corrente (sintetizzo).
foreach (object e in dataItems){
...
MyDataItem item = new MyDataItem(e); //qui gli passo l'oggetto corrente
ItemTemplate.InstantiateIn(item);
Controls.Add(item);
item.DataBind()
...
}


Se Eval non ti dovesse funzionare, sappi che puoi comunque accedere alla Pagina corrente dalla proprietà DataItem che si trova dentro il MyDataItem. Così:
<ItemTemplate>
<%# (Container.DataItem as Pagina).Nome %>
</ItemTemplate>


ciao
Modificato da BrightSoul il 15 novembre 2012 23.47 -

Enjoy learning and just keep making
161 messaggi dal 07 settembre 2009
In breve quando richiamo Sottopagine mi posiziono semplicemente sulla classe che andrò poi a riempire... ora è tutto più chiaro =)

Per quanto riguarda Eval ho risolto come hai detto tu.

Eval lo usavo già, ma visto che nel costruttore di MyDataItem veniva passato il parametro Text di tipo string, il controllo mi accettava solo la voce Text (dando comunque risultati corretti), e per ottenere il campo id avevo creato un secondo costruttore aggiungendo il parametro ID (anche qui per richiamarlo poi dovevo sempre richiamare Eval(ID))

Ora credo che non ci siano più domande... il menù funziona esattamente come volevo... quasi mi dispiace terminare questa discussione tanto appassionante =( ma bisogna andare avanti.


Grazie di tutto

Davide

E anche oggi il mondo è salvo... Ma per quanto?

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.