Come faccio a progettare un DAL quando devo gestire le relazioni?

7

Dire che sto sviluppando un bug tracker, in cui un ticket appartiene a al massimo una pietra miliare, e una pietra miliare può avere molti ticket. Quando una milestone viene cancellata (dal database), tutti i ticket associati a tale milestone devono avere la loro relazione non impostata (cioè impostata su null ). Questa è una relazione semplice.

Le classi sono abbastanza semplici e si collegano direttamente a un database:

public class Ticket {
    public ObjectId Id { get; set; }
    public string Title { get; set; }
    public ObjectId MilestoneId { get; set; }
}

public class Milestone {
    public ObjectId Id { get; set; }
    public string Title { get; set; }
}

Ora arriva la parte difficile: dove e come faccio a ripristinare i campi MilestoneId ? Una possibile soluzione è scrivere un livello di accesso ai dati creando una classe gigante con tutti i metodi di accesso ai dati per tutti i tipi di oggetti, in questo caso biglietti e pietre miliari. Un'altra possibile soluzione è scrivere diverse classi (ad esempio TicketProvider e MilestoneProvider ), una per ciascun tipo di oggetto, che fornisce metodi di accesso ai dati come Find , Save e Destroy .

Quest'ultimo mi attrae di più perché non mi piacciono le classi mostruose, ma c'è un avvertimento: il metodo che cancella le pietre miliari deve reimpostare MilestoneId s di tutti i ticket associati a null . Ciò significa che MilestoneProvider , che è responsabile della manipolazione delle pietre miliari, si occupa improvvisamente di biglietti!

In che modo le relazioni vengono comunemente trattate nei livelli di accesso ai dati e in che modo è possibile prevenire la violazione di SRP? Devo inserire l'intero DAL in una classe, o dovrei separarlo e, in caso affermativo, come posso farlo meglio?

    
posta rightfold 27.12.2012 - 05:42
fonte

3 risposte

2

Suggerirei di racchiudere quella logica nel punto in cui è possibile eliminare la pietra miliare poiché la rimozione dell'ID dai ticket è un dettaglio dell'implementazione della logica aziendale al momento dell'eliminazione. L'importante è farlo in un unico posto ovvio in una transazione in modo che tutto accada o che non accada nulla.

Se stai usando il modello di repository, potresti fare qualcosa seguendo queste linee:

void Delete(int milestoneId)
{
    db.BeginTransaction();
    db.Execute("UPDATE Tickets SET MilestoneID = null WHERE MilestoneID = @p0", milestoneId);
    db.Execute("DELETE FROM Milestones WHERE MilestoneID = @p0", milestoneId);
    db.Commit();
}

È un po 'sottomesso alle preferenze personali qui poichè potresti obiettare che MilestoneRepository non dovrebbe sapere sui Ticket ma dipende da quanti altri requisiti hai in queste righe (ad esempio se non ce ne sono molti) potrebbe essere accettabile per ora. L'altra opzione è creare un servizio che abbia questa conoscenza in questo modo:

class MilestoneRepository
{
    void Delete(int milestoneId)
    {
        db.Execute("DELETE FROM Milestones WHERE MilestoneID = @p0", milestoneId);
    }
}

class TicketRepository
{
    void DetachAllFromMilestone(int milestoneId)
    {
        db.Execute("UPDATE Tickets SET MilestoneID = null WHERE MilestoneID = @p0", milestoneId);
    }
}

class MilestoneService
{
    void Delete(Milestone milestone)
    {
        unitOfWork.Begin(); // start transaction
        ticketRepository.DetachAllFromMilestone(milestone.Id);
        milestoneRepository.Delete(milestone.Id);
        unitOfWork.Complete(); // commit transaction
    }
}
    
risposta data 27.12.2012 - 21:40
fonte
0

In alternativa a farlo nel codice di logica aziendale di livello intermedio, offro un trigger di Microsoft SQL Server:

CREATE TRIGGER dbo.DeleteMilestone
  ON dbo.Milestone
  INSTEAD OF DELETE
AS
  BEGIN TRANSACTION

  UPDATE t
  SET t.MilestoneId = NULL
  FROM dbo.Ticket t
  INNER JOIN DELETED d ON t.MilestoneId = d.Id;

  DELETE m
  FROM dbo.Milestone m
  INNER JOIN DELETED d ON m.Id = d.Id;

  COMMIT TRANSACTION
GO
    
risposta data 27.12.2012 - 23:32
fonte
-4

Crea una proprietà readonly nella classe genitore, controlla se l'id del bambino recuperato è nullo den incrementalo o assegnalo da db.

    
risposta data 27.12.2012 - 17:37
fonte