title: Constructor Dependency Injection alternatives
body: <p>I am working solo on an employee timekeeping project which uses a database to store time sheet entries.  I am using Delphi Pro 10.2.3 Tokyo for the project and have created a library of wrapper classes to facilitate working with datasets like normal classes.  For example, to access the FirstName field in the Employee table, I can write LFirstName := FEmployee.FirstName; rather than LFirstName := Dataset.FieldByName('FirstName').AsString;</p>
  
  <p>Some of my classes have a significant number of dependencies (as many as eight) which I am injecting through the class' constructor.  I use a domain object to create the required interfaces and inject them into the class being created. 
   Some of the interfaces being injected themselves are also very complex and it is starting to get difficult to keep track of everything in the domain object. </p>
  
  <p>The dependencies being injected include wrapper interfaces for other tables which provide lookup values for calculated fields, pointers to functions which create create objects used by the class or call back functions which resolve master/detail relationships.  These relationships are static and need to be set in the constructor so that any calculated fields will function when the class is created.</p>
  
  <p>Are there any alternatives to constructor injection which might may decrease complexity of the constructor while maintaining decoupled classes.  Here is a sample of code from one of my modules for time sheet entries.</p>
  
  <pre><code>unit LevelPay.DbModel.TimesheetEntry;
  
  interface
  
  uses
      Data.Db
    , FireDAC.Comp.DataSet
    , MyLib.Model.Interfaces
    , LevelPay.Model.Types
    , LevelPay.Model.Constants
    , LevelPay.Model.Interfaces
    , LevelPay.DbModel.AppModel
    ;
  
  type
    TDbCustomTimesheetEntry = class(
      TDbAppModel&lt;ITimesheetEntry&gt;,
      ITimesheetEntry
      )
    strict private
      FCopyFunc: TCopyFunc&lt;ITimesheetEntry&gt;;
      procedure ClearFilter;
      procedure FilterEntries(const ADate: TDate);
    strict protected
      FTimesheet: ITimesheet;
      FID: TField;
      FEmployeeID: TField;
      FPayPeriodEndDate: TField;
      FFiscalYearEndDate: TField;
      FFiscalYearStartDate: TField;
      FRowNbr: TField;
      FEntryTypeID: TField;
      FDateIn: TField;
      FTimein: TField;
      FDateOut: TField;
      FTimeOut: TField;
      FCreatedBy: TField;
      FCreatedTimestamp: TField;
      FLastModifiedBy: TField;
      FLastModifiedTimestamp: TField;
      FNote: TField;
      FClockable: TField;
      FClockableHours: TField;
      FDayOfWeek: TField;
      FDifference: TField;
      FEmployeeName: TField;
      FEntryTypeCaption: TField;
      FTimeElapsed: TField;
      FDateIndex: TFDIndex;
      FTimeScheduled: TField;
      FScheduledTimeIn: TField;
      FScheduledTimeOut: TField;
      FWeekOf: TField;
      function GetID: TIdentifier;
      function GetModel: ITimesheetEntry; override;
      function GetClockable: Boolean;
      function GetClockableHours: THours;
      function GetDateIn: TDate;
      function GetDateOut: TDate;
      function GetDifference: THours;
      function GetEmployeeID: TIdentifier;
      function GetEmployeeName: string;
      function GetPayPeriodEndDate: TDate;
      function GetFiscalYearStartDate: TDate;
      function GetFiscalYearEndDate: TDate;
      function GetEntryTypeID: TIdentifier;
      function GetEntryTypeCaption: TCaption;
      function GetPlaceholder: Boolean;
      function GetRowNbr: TRowNbr;
      function GetScheduledTimeIn: TTime;
      function GetScheduledTimeOut: TTime;
      function GetTimeElapsed: THours;
      function GetTimein: TTime;
      function GetTimeOut: TTime;
      function GetTimeScheduled: THours;
      function GetWeekOf: TDate;
      function GetWeekDay: string;
      function GetCreatedBy: TUserName;
      function GetCreatedTimestamp: TDateTime;
      function GetLastModifiedBy: TUserName;
      function GetLastModifiedTimestamp: TDateTime;
      function GetNote: AnsiString;
      function GetEntry: ITimesheetEntry;
      function GetTimesheet: ITimesheet;
      function GetHasEntries: Boolean;
      function Find(AModel: ITimesheetEntry): Boolean; override;
      procedure DoUpdate(AModel: ITimesheetEntry); override;
      procedure Load; virtual;
      procedure CreateFields; override;
      procedure CreateCalcFields; override;
      procedure CreateIndexes; override;
      procedure FormatFields; override;
      procedure OnCalcFields(Dataset: TDataset); override;
      procedure OnNewRecord(Dataset: TDataset); override;
    public
      constructor Create(
        ADataset: TFDDataset;
        AModelFunc: TModelFunc&lt;ITimesheetEntry&gt;;
        ACopyFunc: TCopyFunc&lt;ITimesheetEntry&gt;;
        ATimesheet: ITimesheet;
        ACreateFields: Boolean
      ); reintroduce;
      property ID: TIdentifier read GetID;
      property EmployeeID: TIdentifier read GetEmployeeID;
      property PayPeriodEndDate: TDate read GetPayPeriodEndDate;
      property FiscalYearEndDate: TDate read GetFiscalYearEndDate;
      property FiscalYearStartDate: TDate read GetFiscalYearStartDate;
      property EntryTypeID: TIdentifier read GetEntryTypeID;
      property EntryTypeCaption: TCaption read GetEntryTypeCaption;
      property RowNbr: TRowNbr read GetRowNbr;
      property Clockable: Boolean read GetClockable;
      property ClockableHours: THours read GetClockableHours;
      property DateIn: TDate read GetDateIn;
      property EmployeeName: string read GetEmployeeName;
      property ScheduledTimeIn: TTime read GetScheduledTimeIn;
      property ScheduledTimeOut: TTime read GetScheduledTimeOut;
      property TimeIn: TTime read GetTimein;
      property DateOut: TDate read GetDateOut;
      property TimeOut: TTime read GetTimeOut;
      property TimeElapsed: THours read GetTimeElapsed;
      property Placeholder: Boolean read GetPlaceholder;
      property TimeScheduled: THours read GetTimeScheduled;
      property Difference: THours read GetDifference;
      property WeekDay: string read GetWeekDay;
      property WeekOf: TDate read GetWeekOf;
      property CreatedBy: TUserName read GetCreatedBy;
      property CreatedTimestamp: TDateTime read GetCreatedTimestamp;
      property LastModifiedBy: TUserName read GetLastModifiedBy;
      property LastModifiedTimestamp: TDateTime read GetLastModifiedTimestamp;
      property Note: AnsiString read GetNote;
      property Timesheet: ITimesheet read GetTimesheet;
    end;
  
    TDbSourceEntry = class(TDbCustomTimesheetEntry, ISourceEntryList)
    strict private
      FLoadTimesheetEntries: TLoadTimesheetProc;
      FElectionList: ILevelPayElectionList;
      FPositionList: IHourlyPositionList;
    strict protected
      procedure BeforePost(Dataset: TDataset); override;
      procedure Load; override;
    public
      constructor Create(
        ADataset: TFDDataset;
        AModelFunc: TModelFunc&lt;ITimesheetEntry&gt;;
        ATimesheet: ITimesheet;
        ACopyFunc: TCopyFunc&lt;ITimesheetEntry&gt;;
        AProc: TLoadTimesheetProc;
        AElectionList: ILevelPayElectionList;
        APositionList: IHourlyPositionList;
        ACreateFields: Boolean = True
      ); reintroduce;
    end;
  
    TDbDummyEntry = class(TDbCustomTimesheetEntry, IDummyEntryList)
    strict private
      FPlaceholderID: TIdentifier;
      FClosureList: ISchoolClosureList;
      procedure EntryTypeIDOnChange(Sender: TField);
    strict protected
      procedure AddPlacedholder(ADate: TDate; ARowNbr: TRowNbr);
      procedure CreateFields; override;
      procedure DoAdd(AModel: ITimesheetEntry); override;
      property PlaceholderID: TIdentifier read FPlaceholderID write FPlaceholderID;
    public
      constructor Create(
        ADataset: TFDDataset;
        AModelFunc: TModelFunc&lt;ITimesheetEntry&gt;;
        ACopyFunc: TCopyFunc&lt;ITimesheetEntry&gt;;
        ATimesheet: ITimesheet;
        AClosureList: ISchoolClosureList;
        ACreateFields: Boolean
      ); reintroduce;
    end;
  
    TDbTimesheetEntry = class(TDbDummyEntry, ITimesheetEntryList)
    strict private
      FClone: TFDDataset;
      FSource: ISourceEntryList;
      function GetNextRowNbr: TRowNbr;
    strict protected
      function WorkweekList: IWorkweekList;
      procedure Clear;
      procedure Load; override;
    public
      procedure Add(AModel: ITimesheetEntry); //replaces inherited add
      procedure Delete(AModel: ITimesheetEntry); //replace inherited delete
      procedure Update(OldModel, NewModel: ITimesheetEntry);
      constructor Create(
        ADataset: TFDDataset;
        AModelFunc: TModelFunc&lt;ITimesheetEntry&gt;;
        ACopyFunc: TCopyFunc&lt;ITimesheetEntry&gt;;
        ATimesheet: ITimesheet;
        ASourceFunc: TSourceListFunc;
        AClosureList: ISchoolClosureList;
        ACreateFields: Boolean
      ); reintroduce;
    end;
  
  implementation
  
  uses
      System.SysUtils
    , System.Classes
    , System.Variants
    , System.DateUtils
    , FireDAC.Comp.Client
    , DateTimeHelper
    , LevelPay.Model.Helpers
    ;
  
  { TCustomShift }
  
  procedure TDbCustomTimesheetEntry.ClearFilter;
  begin
    CancelRange;
  end;
  
  constructor TDbCustomTimesheetEntry.Create(ADataset: TFDDataset;
    AModelFunc: TModelFunc&lt;ITimesheetEntry&gt;; ACopyFunc: TCopyFunc&lt;ITimesheetEntry&gt;;
    ATimesheet: ITimesheet; ACreateFields: Boolean);
  begin
    inherited Create(ADataset, AModelFunc, ACreateFields);
    FCopyFunc := ACopyFunc;
    FTimesheet := ATimesheet;
  end;
  
  procedure TDbCustomTimesheetEntry.CreateCalcFields;
  begin
    inherited;
    FClockable           := CreateCalcBooleanField(k_Clockable);
    FClockableHours      := CreateCalcFloatField(k_ClockableHours);
    FDayOfWeek           := CreateCalcStringField(k_WeekDay, 13);
    FDifference          := CreateCalcFloatField(k_Difference);
    FEmployeeName        := CreateCalcStringField(k_EmployeeName, 40);
    FEntryTypeCaption    := CreateCalcStringField(k_EntryTypeCaption, 20);
    FFiscalYearEndDate   := CreateCalcDateTimeField(k_FiscalYearEndDate);
    FFiscalYearStartDate := CreateCalcDateTimeField(k_FiscalYearStartDate);
    FScheduledTimeIn     := CreateCalcDateTimeField(k_ScheduledTimeIn);
    FScheduledTimeOut    := CreateCalcDateTimeField(k_ScheduledTimeOut);
    FTimeElapsed         := CreateCalcFloatField(k_TimeElapsed);
    FTimeScheduled       := CreateCalcFloatField(k_TimeScheduled);
    FWeekOf              := CreateCalcDateTimeField(k_WeekOf);
  end;
  
  procedure TDbCustomTimesheetEntry.CreateFields;
  begin
    FID                    := CreateField(k_Id);
    FEmployeeID            := CreateField(k_EmployeeID);
    FEntryTypeID           := CreateField(k_EntryTypeID);
    FRowNbr                := CreateField(k_RowNbr);
    FTimeIn                := CreateField(k_TimeIn);
    FTimeOut               := CreateField(k_TimeOut);
    FID                    := CreateField(k_ID);
    FDateIn                := CreateField(k_DateIn);
    FDateOut               := CreateField(k_DateOut);
    FCreatedBy             := CreateField(k_CreatedBy);
    FCreatedTimestamp      := CreateField(k_CreatedTimeStamp);
    FLastModifiedBy        := CreateField(k_LastModifiedBy);
    FLastModifiedTimestamp := CreateField(k_LastModifiedTimestamp);
    FNote                  := CreateField(k_Note);
    FPayPeriodEndDate      := CreateField(k_PayPeriodEndDate);
  
  end;
  
  procedure TDbCustomTimesheetEntry.CreateIndexes;
  const
    FIELD_LIST = k_DateIn + ';' + k_RowNbr;
  begin
    inherited;
    FDateIndex := CreateIndex('ByDate', FIELD_LIST);
    FDateIndex.Selected := True;
    Dataset.IndexesActive := True;
  end;
  
  function TDbCustomTimesheetEntry.GetClockable: Boolean;
  begin
    Result := Rules.Clockable;
  end;
  
  function TDbCustomTimesheetEntry.GetClockableHours: THours;
  begin
    Result := Rules.ClockableHours;
  end;
  
  function TDbCustomTimesheetEntry.GetCreatedBy: TUserName;
  begin
    Result := FCreatedBy.AsUserName;
  end;
  
  function TDbCustomTimesheetEntry.GetCreatedTimestamp: TDateTime;
  begin
    Result := FCreatedTimestamp.AsDateTime;
  end;
  
  function TDbCustomTimesheetEntry.GetDateIn: TDate;
  begin
    Result := FDateIn.AsDateTime;
  end;
  
  function TDbCustomTimesheetEntry.GetDateOut: TDate;
  begin
    Result := FDateOut.AsDateTime;
  end;
  
  function TDbCustomTimesheetEntry.GetDifference: THours;
  begin
    Result := Rules.Difference;
  end;
  
  function TDbCustomTimesheetEntry.GetEmployeeID: TIdentifier;
  begin
    Result := FEmployeeID.AsIdentifier;
  end;
  
  function TDbCustomTimesheetEntry.GetEmployeeName: string;
  begin
    Result := Rules.EmployeeName;
  end;
  
  function TDbCustomTimesheetEntry.GetEntryTypeID: TIdentifier;
  begin
    Result := FEntryTypeID.AsIdentifier;
  end;
  
  function TDbCustomTimesheetEntry.GetFiscalYearEndDate: TDate;
  begin
    Result := FFiscalYearEndDate.AsDateTime;
  end;
  
  function TDbCustomTimesheetEntry.GetFiscalYearStartDate: TDate;
  begin
    Result := FFiscalYearStartDate.AsDateTime;
  end;
  
  function TDbCustomTimesheetEntry.GetHasEntries: Boolean;
  begin
    Result := RecordCount &gt; 0;
  end;
  
  function TDbCustomTimesheetEntry.GetID: TIdentifier;
  begin
    Result := FID.AsInteger;
  end;
  
  function TDbCustomTimesheetEntry.GetLastModifiedBy: TUserName;
  begin
    Result := FLastModifiedBy.AsUserName;
  end;
  
  function TDbCustomTimesheetEntry.GetLastModifiedTimestamp: TDateTime;
  begin
    Result := FLastModifiedTimestamp.AsDateTime;
  end;
  
  function TDbCustomTimesheetEntry.GetModel: ITimesheetEntry;
  var
    LResult: ITimesheetEntry;
  begin
    LResult := FCopyFunc(Self);
    Result := LResult;
  end;
  
  function TDbCustomTimesheetEntry.GetNote: AnsiString;
  begin
    Result := FNote.AsAnsiString;
  end;
  
  function TDbCustomTimesheetEntry.GetPayPeriodEndDate: TDate;
  begin
    Result := FPayPeriodEndDate.AsDateTime;
  end;
  
  function TDbCustomTimesheetEntry.GetPlaceholder: Boolean;
  begin
    Result := Rules.Placeholder;
  end;
  
  function TDbCustomTimesheetEntry.GetEntry: ITimesheetEntry;
  begin
    Result := Model;
  end;
  
  function TDbCustomTimesheetEntry.GetEntryTypeCaption: TCaption;
  begin
    Result := Rules.EntryTypeCaption
  end;
  
  function TDbCustomTimesheetEntry.GetRowNbr: TRowNbr;
  begin
    Result := FRowNbr.AsRowNbr;
  end;
  
  function TDbCustomTimesheetEntry.GetScheduledTimeIn: TTime;
  begin
    Result := Rules.ScheduledTimeIn;
  end;
  
  function TDbCustomTimesheetEntry.GetScheduledTimeOut: TTime;
  begin
    Result := Rules.ScheduledTimeOut;
  end;
  
  function TDbCustomTimesheetEntry.GetTimeElapsed: THours;
  begin
    Result := Rules.TimeElapsed;
  end;
  
  function TDbCustomTimesheetEntry.GetTimein: TTime;
  begin
    Result := FTimeIn.AsDateTime;
  end;
  
  function TDbCustomTimesheetEntry.GetTimeOut: TTime;
  begin
    Result := FTimeOut.AsDateTime;
  end;
  
  function TDbCustomTimesheetEntry.GetTimeScheduled: THours;
  begin
    Result := Rules.TimeScheduled;
  end;
  
  function TDbCustomTimesheetEntry.GetTimesheet: ITimesheet;
  begin
    Result := FTimesheet;
  end;
  
  function TDbCustomTimesheetEntry.GetWeekDay: string;
  begin
    Result := Rules.WeekDay;
  end;
  
  function TDbCustomTimesheetEntry.GetWeekOf: TDate;
  begin
    Result := Rules.WeekOf;
  end;
  
  procedure TDbCustomTimesheetEntry.Load;
  begin
    //Stub procedure
  end;
  
  procedure TDbCustomTimesheetEntry.OnCalcFields(Dataset: TDataset);
  begin
    inherited;
    if not Assigned(Rules) then Exit;
  
    FClockable.AsBoolean         := GetClockable;
    FClockableHours.AsHours      := GetClockableHours;
    FDayOfWeek.AsString          := GetWeekDay;
    FDifference.AsHours          := GetDifference;
    FEmployeeName.AsString       := GetEmployeeName;
    FEntryTypeCaption.AsCaption  := GetEntryTypeCaption;
    FTimeElapsed.AsHours         := GetTimeElapsed;
    FTimeScheduled.AsHours       := GetTimeScheduled;
    FScheduledTimeIn.AsDateTime  := GetScheduledTimeIn;
    FScheduledTimeOut.AsDateTime := GetScheduledTimeOut;
    FWeekOf.AsDateTime           := GetWeekOf;
  end;
  
  procedure TDbCustomTimesheetEntry.OnNewRecord(Dataset: TDataset);
  begin
    inherited;
  
    FEmployeeID.AsIdentifier        := FTimesheet.EmployeeID;
    FFiscalYearEndDate.AsDateTime   := FTimesheet.FiscalYearEndDate;
    FFiscalYearStartDate.AsDateTime := FTimesheet.FiscalYearStartDate;
    FPayPeriodEndDate.AsDateTime    := FTimesheet.PayPeriodEndDate;
  end;
  
  procedure TDbCustomTimesheetEntry.DoUpdate(AModel: ITimesheetEntry);
  begin
    inherited;
  
    FEmployeeID.AsIdentifier  := AModel.EmployeeID;
    FRowNbr.AsRowNbr          := AModel.RowNbr;
    FEntryTypeID.AsIdentifier := AModel.EntryTypeID;
    FDateIn.AsDateTime        := AModel.DateIn;
    FTimeIn.AsDateTime        := AModel.TimeIn;
    FDateOut.AsDateTime       := AModel.DateOut;
    FTimeOut.AsDateTime       := AModel.TimeOut;
    FNote.AsAnsiString        := AModel.Note;
  end;
  
  procedure TDbCustomTimesheetEntry.FilterEntries(const ADate: TDate);
  begin
    FDateIndex.Selected := True;
    SetRange([ADate], [ADate]);
  end;
  
  function TDbCustomTimesheetEntry.Find(AModel: ITimesheetEntry): Boolean;
  begin
    Result := Locate(k_ID, AModel.ID);
  end;
  
  procedure TDbCustomTimesheetEntry.FormatFields;
  begin
    inherited;
    FTimeElapsed.OnGetText    := HoursFieldGetText;
    FClockableHours.OnGetText := HoursFieldGetText;
    FDifference.OnGetText     := HoursFieldGetText;
    FTimeScheduled.OnGetText  := HoursFieldGetText;
  
    SetTimeFieldDisplayFormat(FTimeIn);
    SetTimeFieldDisplayFormat(FTimeOut);
    SetDateFieldDisplayFormat(FDateIn);
    SetDateFieldDisplayFormat(FDateOut);
    SetDateFieldDisplayFormat(FPayPeriodEndDate);
    SetSQLTimestampFieldDisplayFormat(FCreatedTimestamp);
    SetSQLTimestampFieldDisplayFormat(FLastModifiedTimestamp);
  end;
  
  { TDbDummyEntry }
  
  procedure TDbSourceEntry.BeforePost(Dataset: TDataset);
  var
    LTimestamp: TDateTime;
  begin
    inherited;
    LTimestamp := Now;
    FLastModifiedBy.AsUserName        := FTimesheet.User.UserName;
    FLastModifiedTimestamp.AsDateTime := LTimestamp;
    if State in [dsInsert] then
    begin
      FCreatedBy.AsUserName        := FTimesheet.User.UserName;
      FCreatedTimestamp.AsDateTime := LTimestamp;
    end;
  end;
  
  constructor TDbSourceEntry.Create(ADataset: TFDDataset;
    AModelFunc: TModelFunc&lt;ITimesheetEntry&gt;; ATimesheet: ITimesheet;
    ACopyFunc: TCopyFunc&lt;ITimesheetEntry&gt;; AProc: TLoadTimesheetProc;
    AElectionList: ILevelPayElectionList;   APositionList: IHourlyPositionList;
    ACreateFields: Boolean);
  begin
    inherited Create(ADataset, AModelFunc, ACopyFunc, ATimesheet, ACreateFields);
  
    FLoadTimesheetEntries := AProc;
    FElectionList         := AElectionList;
    FPositionList         := APositionList;
  end;
  
  procedure TDbSourceEntry.Load;
  var
    LEmployeeID: TIdentifier;
    LFirstEntryDate: TDate;
    LLastEntryDate: TDate;
  begin
    LEmployeeID     := FTimesheet.EmployeeID;
    LFirstEntryDate := FTimesheet.FirstEntryDate;
    LLastEntryDate  := FTimesheet.LastEntryDate;
    FLoadTimesheetEntries(LEmployeeID, LFirstEntryDate, LLastEntryDate);
  end;
  
  
  { TDbDummyEntry }
  
  procedure TDbDummyEntry.AddPlacedholder(ADate: TDate; ARowNbr: TRowNbr);
  var
    LEntryTypeID: TIdentifier;
    LClosure: ISchoolClosure;
    LNote: AnsiString;
    LRowNbr: TRowNbr;
  begin
    Dec(FPlaceholderID);
    LNote        := '';
    LEntryTypeID := 0;
    LRowNbr      := ARowNbr;
  
    if LRowNbr &lt; 2 then //This is a first entry for the date
    begin
    if FClosureList.Find(ADate) then
      begin
        LClosure := FClosureList.Closure;
        LEntryTypeID := LClosure.EntryTypeID;
        LNote := AnsiString(LClosure.Caption);
      end
      else
      begin
        if TDateTime(ADate).DayOfWeek in [MONDAY..FRIDAY] then
          LEntryTypeID := k_Regular
        else
          LEntryTypeID := 0;
      end;
    end;
  
    Append;
    FId.AsIdentifier          := FPlaceholderID;
    FRowNbr.AsRowNbr          := LRowNbr;
    FDateIn.AsDateTime        := ADate;
    FDateOut.AsDateTime       := ADate;
    FEntryTypeID.AsIdentifier := LEntryTypeID;
    FNote.AsAnsiString        := LNote;
    Post;
  end;
  
  constructor TDbDummyEntry.Create(ADataset: TFDDataset;
    AModelFunc: TModelFunc&lt;ITimesheetEntry&gt;; ACopyFunc: TCopyFunc&lt;ITimesheetEntry&gt;;
    ATimesheet: ITimesheet; AClosureList: ISchoolClosureList; ACreateFields: Boolean);
  begin
    inherited Create(ADataset, AModelFunc, ACopyFunc, ATimesheet, ACreateFields);
    FClosureList  := AClosureList;
  end;
  
  procedure TDbDummyEntry.CreateFields;
  begin
    inherited;
    FId.ReadOnly := False;
    FId.AutoGenerateValue := arNone;
    FEntryTypeID.OnChange := EntryTypeIDOnChange;
  end;
  
  procedure TDbDummyEntry.DoAdd(AModel: ITimesheetEntry);
  begin
    inherited;
    FId.AsIdentifier := AModel.ID;
  end;
  
  procedure TDbDummyEntry.EntryTypeIDOnChange(Sender: TField);
  var
    LDateIn: TDate;
    LDateOut: TDate;
  begin
    if Sender.AsIdentifier = k_LandSchool then
    begin
      LDateIn  := FDateIn.AsDateTime;
      LDateOut := LDateIn + 1;
      FDateOut.AsDateTime := LDateOut;
      FTimeIn.AsDateTime  := GetScheduledTimeOut;
      FTimeOut.AsDateTime := GetScheduledTimeIn;
    end;
  end;
  
  { TDbTimesheetEntry }
  
  procedure TDbTimesheetEntry.Add(AModel: ITimesheetEntry);
  var
    LEntry: ITimesheetEntry;
  begin
    FSource.Add(AModel);
    LEntry := FSource.Entry; //Get model with updated ID
    FTimesheet.WorkweekList.AddEntry(LEntry);
    inherited Add(LEntry);
  end;
  
  procedure TDbTimesheetEntry.Clear;
  begin
    PlaceholderID := 0;
    Dataset.CancelUpdates;
    DisableControls;
    try
      First;
      while RecordCount &gt; 0 do
        Dataset.Delete;
    finally
      EnableControls;
    end;
  end;
  
  constructor TDbTimesheetEntry.Create(ADataset: TFDDataset;
    AModelFunc: TModelFunc&lt;ITimesheetEntry&gt;;
    ACopyFunc: TCopyFunc&lt;ITimesheetEntry&gt;; ATimesheet: ITimesheet;
    ASourceFunc: TSourceListFunc; AClosureList: ISchoolClosureList;
    ACreateFields: Boolean);
  begin
    inherited Create(ADataset, AModelFunc, ACopyFunc, ATimesheet, AClosureList, ACreateFields);
    FSource := ASourceFunc(FTimesheet);
  
    FClone := TFDMemTable.Create(ADataset);
    FClone.CloneCursor(ADataset);
  end;
  
  procedure TDbTimesheetEntry.Delete(AModel: ITimesheetEntry);
  var
    LDate: TDate;
    LRowNbr: SmallInt;
    LEntry: ITimesheetEntry;
  begin
    FSource.Delete(AModel);
    WorkweekList.DeleteEntry(AModel);
    DisableControls;
    try
      LDate := AModel.DateIn;
      LRowNbr := AModel.RowNbr;
      inherited Delete(AModel);
      if (LRowNbr = 1) then
      begin
        AddPlacedholder(LDate, 0);
        LEntry := Model; //Get placeholder
        WorkweekList.AddEntry(LEntry);
      end;
    finally
      EnableControls;
    end;
  end;
  
  function TDbTimesheetEntry.GetNextRowNbr: TRowNbr;
  begin
    Result := 0;
    FClone.SetRange([GetDateIn], [GetDateIn]);
    while not FClone.Eof do
    begin
      Result := FClone.FieldByName(k_RowNbr).AsRowNbr + 1;
      FClone.Next;
    end;
    FClone.CancelRange;
  end;
  
  procedure TDbTimesheetEntry.Load;
  var
    LDate: TDate;
    LFirstEntryDate: TDate;
    LCutoffDate: TDate;
    LEntry: ITimesheetEntry;
    LWeekOf: TDate;
    LWorkweekList: IWorkweekList;
  begin
    LWorkweekList   := FTimesheet.WorkweekList;
    LFirstEntryDate := FTimesheet.FirstEntryDate;
    LCutOffDate     := FTimesheet.LastEntryDate;
    LWeekOf         := TDateTime(LFirstEntryDate).StartOfWeek;
  
    LWorkweekList.Init(LFirstEntryDate, LCutOffDate);
    FSource.Load;
    DisableControls;
    try
      Clear;
  
      LDate := LWeekOf;
      repeat
        FSource.FilterEntries(LDate);
        if FSource.HasEntries then
        begin
          for LEntry in FSource do
          begin
            if LDate &gt;= LFirstEntryDate then
            begin
              inherited Add(LEntry);  //this must call inherited add to adding to FSource
            end;
            LWorkweekList.AddEntry(LEntry);
          end;
        end
        else
        begin
          if LDate &gt;= LFirstEntryDate then
          begin
            AddPlacedholder(LDate, 0);
            LEntry := GetModel; //This retrieves the placeholder
            LWorkweekList.AddEntry(LEntry);
          end;
        end;
        LDate := LDate + 1;
      until LDate &gt; LCutOffDate;
    finally
      FSource.ClearFilter;
      EnableControls;
      First;
    end;
  end;
  
  procedure TDbTimesheetEntry.Update(OldModel, NewModel: ITimesheetEntry);
  var
    LEntry: ITimesheetEntry;
  begin
    DisableControls;
    try
      if Assigned(OldModel) then
      begin
        if OldModel.Placeholder then
        begin
          FSource.Add(NewModel);
        end
        else
        begin
          FSource.Update(NewModel);
        end;
        inherited Delete(OldModel);
        WorkweekList.DeleteEntry(OldModel);
      end
      else
      begin
        FSource.Add(NewModel);
      end;
  
      LEntry := FSource.Entry; //Get entry with new ID
      inherited Add(LEntry);
      WorkweekList.AddEntry(LEntry);
    finally
      EnableControls;
    end;
  end;
  
  function TDbTimesheetEntry.WorkweekList: IWorkweekList;
  begin
    Result := FTimesheet.WorkweekList;
  end;
  
  end.
  </code></pre>