2010-05-18 16 views
12

Ho alcuni tipi che estendono un tipo comune e questi sono i miei modelli.Problema nel determinare come ordinare i tipi F # a causa di riferimenti circolari

Quindi ho i tipi DAO per ogni tipo di modello per le operazioni CRUD.

Ora ho bisogno di una funzione che mi consenta di trovare un ID dato qualsiasi tipo di modello, quindi ho creato un nuovo tipo per alcune funzioni varie.

Il problema è che non so come ordinare questi tipi. Attualmente ho modelli prima di Dao, ma ho in qualche modo bisogno di DAOMisc prima di CityDAO e CityDAO prima di DAOMisc, che non è possibile.

L'approccio semplice sarebbe quello di mettere questa funzione in ogni DAO, riferendosi ai soli tipi che possono venire prima, così, State viene prima City come State ha una relazione di chiave esterna con City, quindi la funzione ausiliaria sarebbe molto corto. Ma questo mi sembra sbagliato, quindi non sono sicuro di come affrontarlo al meglio.

Ecco il mio tipo miscellaneo, dove BaseType è un tipo comune per tutti i miei modelli.

type DAOMisc = 
    member internal self.FindIdByType item = 
     match(item:BaseType) with 
     | :? StateType as i -> 
      let a = (StateDAO()).Retrieve i 
      a.Head.Id 
     | :? CityType as i -> 
      let a = (CityDAO()).Retrieve i 
      a.Head.Id 
     | _ -> -1 

Ecco un tipo dao. CommonDAO ha effettivamente il codice per le operazioni CRUD, ma qui non è importante.

type CityDAO() = 
    inherit CommonDAO<CityType>("city", ["name"; "state_id"], 
     (fun(reader) -> 
      [ 
       while reader.Read() do 
        let s = new CityType() 
        s.Id <- reader.GetInt32 0 
        s.Name <- reader.GetString 1 
        s.StateName <- reader.GetString 3 
      ]), list.Empty 
    ) 

Questo è il mio tipo di modello:

type CityType() = 
    inherit BaseType() 
    let mutable name = "" 
    let mutable stateName = "" 
    member this.Name with get() = name and set restnameval=name <- restnameval 
    member this.StateName with get() = stateName and set stateidval=stateName <- stateidval 
    override this.ToSqlValuesList = [this.Name;] 
    override this.ToFKValuesList = [StateType(Name=this.StateName);] 

Lo scopo di questa funzione FindIdByType è che voglio trovare l'id di una relazione di chiave esterna, quindi posso impostare il valore nel mio modello e quindi fare in modo che le funzioni CRUD eseguano le operazioni con tutte le informazioni corrette. Quindi, City ha bisogno dell'ID per il nome dello stato, quindi dovrei ottenere il nome dello stato, inserirlo nel tipo state, quindi chiamare questa funzione per ottenere l'id per quello stato, quindi la mia inserzione di città includerà anche l'id per l'estero chiave.

Questo sembra essere l'approccio migliore, in un modo molto generico per gestire gli inserti, che è il problema corrente che sto cercando di risolvere.

UPDATE:

ho bisogno di ricerca e vedere se posso in qualche modo iniettare il metodo FindIdByType nella CommonDAO dopo sono stati definiti tutti gli altri DAO, quasi come se si tratta di una chiusura. Se questo fosse Java, userei AOP per ottenere la funzionalità che sto cercando, non certo come farlo in F #.

aggiornamento finale:

Dopo pensare al mio approccio che ho capito che era fatalmente difettosa, così mi si avvicinò con un approccio diverso.

In questo modo inserirò un inserto e ho deciso di inserire questa idea in ogni classe di entità, che è probabilmente un'idea migliore.

member self.Insert(user:CityType) = 
    let fk1 = [(StateDAO().Retrieve ((user.ToFKValuesList.Head :?> StateType), list.Empty)).Head.Id] 
    self.Insert (user, fk1) 

non ho iniziato ad utilizzare il fklist ancora, ma è int list e so che nome della colonna va con ciascuno di essi, quindi ho solo bisogno di fare inner join per seleziona, per esempio.

Questo è il tipo di base inserto che è generalizzato:

member self.Insert(user:'a, fklist) = 
    self.ExecNonQuery (self.BuildUserInsertQuery user) 

Sarebbe bello se F # poteva fare co/contra-varianza, quindi ho dovuto aggirare questa limitazione.

+0

Vedi anche http: // StackOverflow .com/questions/1378575/f-forward-type-declarations – Brian

+0

@Brian - Fino a quando ho pensato a questa altra domanda non ho considerato solo l'utilizzo e tra diversi tipi. Sembra un trucco per un design imperfetto, da parte mia, ma penso che sia l'approccio migliore. –

risposta

7

Questo esempio è molto lontano da quello a cui sono abituato nella programmazione funzionale. Ma per il problema di ordinare tipi reciprocamente ricorsivi, esiste una soluzione standard: usa i parametri del tipo e crea i tipi a due livelli. Darò un semplice esempio in OCaml, una lingua correlata. Non so come tradurre il semplice esempio nelle funzioni di tipo spaventoso che stai utilizzando.

Ecco che cosa non funziona:

type misc = State of string 
      | City of city 

type city = { zipcode : int; location : misc } 

Ecco come risolvere il problema con i tipi a due livelli:

type 'm city' = { zipcode : int; location : 'm } 

type misc = State of string 
      | City of misc city' 
type city = misc city' 

Questo esempio è OCaml, ma forse si può generalizzata a F #. Spero che questo ti aiuti.

+1

Ho bisogno di riflettere su ciò che stai mostrando qui, sembra che questo potrebbe essere un approccio migliore di quello che sto facendo. –

+0

Dopo aver riflettuto su ciò che hai scritto, mi sono reso conto che avevi ragione sul fatto che il mio approccio era sbagliato. –

+0

Ho dovuto riflettere per qualche istante anche su questa bella soluzione :) –

10

In F #, è possibile definire tipi reciprocamente ricorsivi, ovvero, è possibile definire i due tipi che devono fare riferimento l'un l'altro e si vedranno. La sintassi per la scrittura di questo è:

type CityDAO() = 
    inherit CommonDAO<CityType>(...) 
    // we can use DAOMisc here 

and DAOMisc = 
    member internal self.FindIdByType item = 
    // we can use CityDAO here 

Il limite di questa sintassi è che entrambi i tipi devono essere dichiarate in un unico file, in modo non è possibile utilizzare la tipica C# organizzazione 1 tipo per 1 file.

Come fa notare Norman, questo non è un tipico design funzionale, quindi se si è progettato l'intero livello di accesso ai dati in un modo più funzionale, si potrebbe probabilmente evitare questo problema. Tuttavia, direi che non c'è niente di sbagliato nel combinare lo stile funzionale e orientato agli oggetti in F #, quindi l'uso di tipi ricorsivi reciproci potrebbe essere l'unica opzione.

Probabilmente si può scrivere il codice più bene se si definisce prima interfacce per i due tipi - questi possono essere o non hanno bisogno di essere reciprocamente ricorsivo (a seconda se si è utilizzato nell'interfaccia pubblica dell'altro):

type ICityDAO = 
    abstract Foo : // ... 

type IDAOMisc = 
    abstract Foo : // ... 

Questo offre i seguenti vantaggi:

  • Definizione tutte le interfacce reciprocamente ricorsive in un singolo file non rendere il codice meno leggibile
  • in seguito è possibile fare riferimento alle interfacce, in modo che nessun altri tipi devono essere reciprocamente ricorsivo
  • Come effetto collaterale, avrete il codice più estensibile (grazie a interfacce)
+0

Probabilmente avrò un'interfaccia, ma non sono contento della soluzione, dato che sembra essere kludgy. Penso che ci sia qualcosa di fondamentalmente sbagliato nel mio progetto. Ho aggiunto una modifica alla mia domanda al riguardo. –

2

Come di eliminare DAOMisc.FindIdByType, e la sua sostituzione con un FindId all'interno di ogni DAO classe? FindId saprebbe solo come trovare il proprio tipo. Ciò eliminerebbe la necessità per la classe base e il test di tipo dinamico e la dipendenza circolare tra DAOMisc e tutte le altre classi DAO.I tipi DAO possono dipendere l'uno dall'altro, quindi CityDAO può chiamare StateDAO.FindId. (I tipi DAO potrebbero dipendere l'un l'altro a vicenda, se necessario.)

È questo ciò di cui stavi parlando quando hai detto: "L'approccio semplice sarebbe quello di mettere questa funzione in ogni DAO ... Ma, questo solo mi sembra sbagliato ... "? Non sono sicuro perché hai detto che la funzione si riferirebbe solo ai tipi che vengono prima di essa. L'idea che sto presentando qui è che ogni funzione FindId conosce solo il suo tipo.

+0

Sto cercando di ridurre il più possibile il codice ripetitivo e di mantenere le mie funzioni il più generiche possibile, motivo per cui ripetere la funzione in ogni classe dao sarebbe un problema. –

+1

La tua idea era ciò che sono andato con –

3

F # supporta direttamente i tipi reciprocamente ricorsivi. Si consideri la seguente definizione del tipo di pollo/uova:

type Chicken = 
    | Eggs of Egg list 
and Egg = 
    | Chickens of Chicken list 

Il punto è che i tipi mutuamente ricorsivi sono dichiarati insieme usando la 'e' operatore (al contrario di due tipi distinti)

+0

Credo di aver provato il tuo approccio e quando ho scritto la mia domanda non riuscivo a farlo funzionare. F # 3.0 potrebbe aver reso possibile tutto questo, non l'ho provato a questo punto. –