5

Questa sarà una domanda un po 'astratta.Come evitare la duplicazione del codice durante la modellazione di una tabella, il suo layout e i relativi record, che condividono tutti la stessa struttura di base?

Sto lavorando su un framework di Data Access Layer che deve distinguere tra una tabella, il suo schema/layout astratto e i record di tabella concreti. Temo che a causa di questa distinzione, ci sarà molta duplicazione del codice. Potrei aver bisogno di alcuni suggerimenti su come evitare questo.

+-----------+ 
| Foo | 
+-----------+ 
| +Id: Guid | 
+-----------+ 

noti che questo schema potrebbe descrivere qualsiasi di questi: uno schema di tabella, una tabella di cemento, o un record della tabella calcestruzzo, avente un campo Id con tipo Guid.

  • Tutto ciò che è noto nello schema è il nome e il tipo del campo.
  • Nella tabella concreta (aperta), l'"indice di colonna" del campo è inoltre noto.
  • Con i record, tutte queste cose sono note e il campo ha un valore concreto.

Traducendo questo codice, otterrei molti tipi simili (in coppie di tre). Userò le interfacce per mantenere l'esempio breve; quello che voglio mostrare è la somiglianza dei tipi:

// these interfaces only need to be implemented once: 

interface ISchemaField<T> { string Name { get; }  } 


interface ITableField<T> { string Name { get; }   
           int Index { get; }  } 

interface IRecordField<T> { string Name { get; }   
           int Index { get; }  
           T  Value { get; set; } } 

// these three interfaces are an example for one entity; there would be 
// three additional types for each additional entity. 

interface IFooSchema 
{ 
    ISchemaField<Guid> Id { get; } 
    IFooTable Open(IDbConnection dbConnection, string tableName); 
} 

interface IFooTable 
{ 
    ITableField<Guid> Id { get; } 
    ICollection<IFooRecord> ExecuteSomeQuery(); 
} 

interface IFooRecord 
{ 
    IRecordField<Guid> Id { get; } 
} 

Ora vorrei evitare di dover scrivere tre implementazioni molto simili per ogni entità in un modello di dati concreti. Quali sono alcuni modi possibili per ridurre la duplicazione del codice?

  • ho pensato di generazione di codice (ad esempio T4), che sarebbe una soluzione bene, ma io preferirei una soluzione "manualmente" in codice (se ne esiste uno) con un minor numero di righe di codice più leggibile & codice.

  • Ho pensato di creare una classe per entità che implementa lo schema, la tabella e l'interfaccia di registrazione tutti allo stesso tempo ... ma ciò sembra disordinato e come una violazione di Separation of Concerns.

+0

Stai usando Entity Framework? – TheBoyan

+0

@Bojan, no. Sto creando un framework DAL personalizzato su un'API (ArcObjects ESRI) che non è supportato da Entity Framework, né da NHibernate, né da qualsiasi altro OR/M che conosca. – stakx

+0

Solo un breve sguardo al tuo schema mi fa domandare perché non ci sia ereditarietà tra le tre interfacce dello schema? Sembra che duplichi semplicemente i metadati invece di ereditarli. –

risposta

1

IMHO, penso che stai andando un po 'troppo lontano con l'astrazione della struttura del tavolo. L'uso eccessivo delle interfacce può rendere difficile la lettura del codice. Trovo molto fastidioso, ad esempio, che non è possibile premere F12 per vedere l'implementazione di un oggetto perché è un tipo di interfaccia.

Un modello collaudato con cui ho lavorato per anni ed è semplicissimo da mantenere è quello di mantenere tutti i nomi di classe identici ai nomi delle tabelle, quindi i nomi dei campi che corrispondono ai nomi delle colonne.È quindi possibile generare facilmente metodi semplicemente utilizzando la ricerca e la sostituzione nell'editor, e lo stesso vale per le modifiche al codice. In questo modo non è necessario mantenere i nomi delle colonne e gli indici delle colonne in memoria. Basta codificarli nel tuo livello di accesso ai dati (HARDCODING NON ​​È SEMPRE MALE!). Ad esempio:

this.Price = reader["Price"] as decimal?; 

Le prestazioni sono molto buone con questo approccio e il codice è super-gestibile!

Qualunque sia l'approccio che segui, la cosa fondamentale è come mantenere i mapping dalle colonne della tabella alle proprietà della classe. Vi consiglio di codificarli e utilizzare una convenzione di denominazione semplice (nome colonna = nome proprietà). Questo approccio richiede di ricompilare quando si aggiungono o si modificano le colonne, ma penso che queste modifiche non si verifichino abbastanza spesso da giustificare la presenza dei nomi delle colonne nelle variabili o la lettura da un file separato. Ciò evita la ricompilazione, ma rende il codice meno facile da seguire. Non penso ne valga la pena.

+0

Nel mio esempio ho utilizzato interfacce perché ritenevo che un'implementazione concreta avrebbe distratto solo il problema reale.Le interfacce sono sufficienti per mostrare dove si verificherà la duplicazione del codice. Ma avrei potuto usare 'class' invece di' interface'. (Ho aggiornato la mia domanda per renderlo più chiaro.) – stakx

+0

Non sono d'accordo con tutto quello che stai dicendo, esp. indice bit di colonna hardcoding (sembra una potenziale fonte di errori), ma accetto la tua risposta perché la tua affermazione, "stai andando un po 'troppo lontano con l'astrazione della struttura della tabella", ora mi sembra molto ragionevole. Ho postato una risposta separata con quello che ho deciso di fare (vedi sotto). – stakx

+1

Complimenti per aver progettato tutto questo da solo, senza seguire uno schema consigliato da Microsoft o da chiunque. Ho aggiornato la mia risposta per cercare di giustificare il mio schema hardcoded. – Diego

1

Dai un'occhiata allo AutoMapper. È una piccola libreria utile per convertire DTO in POCO ecc. E rimuoverà una grande quantità di codice duplicato e sforzi per trasferire i dati tra i livelli dell'applicazione.

+0

L'API di database sottostante (ESRI ArcObjects) presenta diverse differenze concettuali rispetto al solito RDBMS. (Per prima cosa, è basato su COM.Per un altro, le righe restituite da un cursore DB possono "scadere" non appena viene restituita la riga successiva. Infine, i cursori devono essere liberati in modo esplicito.La lista potrebbe continuare all'infinito.) - Sono abbastanza sicuro che AutoMapper, né i più sofisticati framework OR/M, avranno difficoltà a gestirlo correttamente. Ma mi piacerebbe essere smentito. – stakx

1

Creare una gerarchia di ereditarietà di interfaccia

interface ISchemaField<T> 
{ 
    string Name { get; } 
} 

interface ITableField<T> : ISchemaField<T> 
{ 
    int Index { get; } 
} 

interface IRecordField<T> : ITableField<T> 
{ 
    T Value { get; set; } 
} 

Classi di applicazione di queste interfacce possono poi seguire lo stesso schema di eredità.

+0

Il vero lavoro inizia quando devi * implementare * questi. E ancora peggio del '... Field ' tipi di framework (che devono essere implementati * solo una volta *) sono i tipi di entità triple (che devono essere implementati * per entità *), di cui 'IFoo ... ' è solo uno esempio. – stakx

0

ho accettato un'altra risposta, ma ho pensato di condividere quello che ho finalmente deciso di fare:

  • sto rimuovendo alcuni doppioni fondendo lo schema della tabella e la tabella in un tipo . Questo diventa un tipo di stato: una tabella può essere "non aperta" o "aperta". Se non è aperto, sono disponibili solo i nomi e i tipi di campo. Se la tabella è stata aperta, sono disponibili anche gli indici di campo.

  • I nomi dei campi sono campi statici, o semplicemente costanti stringa, nel tipo di tabella.

  • Mi ritrovo quindi con qualche doppione rimasta, vale a dire la somiglianza di due tipi, uno per un singolo record, l'altro per la tabella/schema, ma questo è accettabile.

Per riassumere, ho scelto una soluzione più pragmatica di quanto avrei voluto; non è così puro, ma probabilmente porterà a termine le cose.


class FooRecord 
{ 
    FooRecord(FooTable table) { … } 
    readonly FooTable table; 

    public Guid Id { get { … } set { … } } 
} 

class TableField<T> 
{ 
    public TableField(string name) { … } 
    public string Name { get; } 
    public int Index { get; internal set; } 
} 

class FooTable 
{ 
    public void Open(IDbConnection dbConnection, string tableName) { … } 
    //^opens the table and determines the fields' indices, e.g. Id.Index. 

    public TableField<Guid> Id 
    { 
     get 
     { 
      return idField; 
     } 
    } 
    TableField<Guid> idField = new TableField<Guid>("ID"); 
} 
Problemi correlati