What am I doing wrong?

What am I doing wrong?

Old forum URL: forums.lhotka.net/forums/t/6919.aspx


RedShiftZ posted on Friday, May 08, 2009

Using CSLA 3.6.2... Winforms...

Object schema: (BusinessListBase) AppoinmentList -> (BusinessBase) Appointment

// Declaration
AppointmentList fApptList = AppointmentList.GetAppointmentList()

// Usage
// Attach to BindingSource to show in grid...
BindingSource.DataSource = fApptList;

private void button1_Click(object sender, EventArgs e)
{
  Appointment apt = fApptList.AddNew();
  apt.Subject = "Testing";
  fApptList.Save();
  gridMain.Refresh(); //Refresh the grid...
}

//Relevant Object Methods
AppointmentList -> DataPortal_Update()
{
      foreach (Appointment apt in this)
      {
        if (apt.IsDirty)
          apt.Save();
      }
}

Appointment -> DataPortal_Insert()
{
// Setup Output Params
SqlParameter parmA = new SqlParameter("@AppointmentID", SqlDbType.Int);
parmA.Direction = ParameterDirection.Output;
cmd.Parameters.Add(parmA);

SqlParameter parmB = new SqlParameter("@NewTStamp", SqlDbType.Timestamp);
parmB.Direction = ParameterDirection.Output;
cmd.Parameters.Add(parmB);

//Execute the Insert..           
cmd.ExecuteNonQuery();

// Reload the changed Properties from the cmd.
LoadProperty(AppointmentIDProperty, (int)parmA.Value);
_tStamp = (byte[])parmB.Value;
}

It saves to the database correctly, but according to the books, it should return a new object back from the database into the list... It doesn't as far as I can tell. The timestamp is null in fApptList after executing this code...

So... What am I doing wrong? I can upload the test project if that would help.


Fintanv replied on Friday, May 08, 2009

It can depend on how you are executing the save.  It should be something like:

fApptList = fApptList.Save();

Also your child save pattern looks strange.  You shouldn't be calling Save() for each child.

RedShiftZ replied on Friday, May 08, 2009

Fintanv:

It can depend on how you are executing the save.  It should be something like:

fApptList = fApptList.Save();

Also your child save pattern looks strange.  You shouldn't be calling Save() for each child.



I showed the Save method in the code listing...
fApptList.Save();

So maybe that's where I went wrong... testing now...

Is there not a difference between a parent/child relationship and a list/object relationship?
I had meant it to be a list, not a parent.

Fintanv replied on Friday, May 08, 2009

In 3.6.2 the parent list will automatically propagate update calls to the child objects.  From the code you provided it appears that you are not using an EditableRootList (very specific use case for this), so all you should have to do is provide the relevant child methods as listed below.  The data portal will find them and invoke. You also do not need to provide the DataPortal_Update in the parent list.  

private void Child_Update()

private void Child_Insert()

private void Child_DeleteSelf()

RedShiftZ replied on Friday, May 08, 2009

Fintanv:
In 3.6.2 the parent list will automatically propagate update calls to the child objects.  From the code you provided it appears that you are not using an EditableRootList (very specific use case for this), so all you should have to do is provide the relevant child methods as listed below.  The data portal will find them and invoke. You also do not need to provide the DataPortal_Update in the parent list.  

private void Child_Update()

private void Child_Insert()

private void Child_DeleteSelf()



Ok, I have rewritten both objects ... Still not working. Same as before...

AppoinmentList.cs
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Text;
using System.ComponentModel;
using Csla;
using Csla.Data;

namespace MyLib.Scheduling
{
  [Serializable()]
  public class AppointmentList : BusinessListBase<AppointmentList, Appointment>
  {
    #region Business Properties and Methods

    public void Remove(int appointmentID)
    {
      foreach (Appointment item in this)
      {
        if (item.AppointmentID == appointmentID)
        {
          Remove(item);
          break;
        }
      }
    }

    public Appointment GetAppointmentById(int appointmentID)
    {
      foreach (Appointment item in this)
      {
        if (item.AppointmentID == appointmentID)
        {
          return item;
        }
      }
      return null;
    }

    protected override object AddNewCore()
    {
      Appointment item = Appointment.NewAppointment();
      Add(item);
      return item;
    }
    #endregion


    #region  Factory Methods

    public static AppointmentList GetAppointmentList()
    {
      return DataPortal.Fetch<AppointmentList>();
    }

    private AppointmentList()
    { /* require use of factory methods */ }

    protected override void OnDeserialized()
    {
      base.OnDeserialized();
    }

    #endregion

    #region  Data Access

    #region Data Access - Fetch
    private void DataPortal_Fetch()
    {
      this.RaiseListChangedEvents = false;
      using (var mgr = ConnectionManager<SqlConnection>.GetManager("MyLib"))
      {
        using (var cmd = mgr.Connection.CreateCommand())
        {
          cmd.CommandType = System.Data.CommandType.StoredProcedure;
          cmd.CommandText = "dsSchedulingAppointments_SelectList";

          cmd.Parameters.AddWithValue("@ClinicID", 1);

          using (var dr = new SafeDataReader(cmd.ExecuteReader()))
          {
            while (dr.Read())
            {
              Appointment apt = Appointment.GetAppointment(dr);
              this.Add(apt);
            }
          }
        }//using
      }//using
      this.RaiseListChangedEvents = true;
      OnFetched();
    }

    partial void OnFetched();

    #endregion

    #region Data Access - Update
    protected override void DataPortal_Update()
    {
      bool cancel = false;
      OnUpdating(ref cancel);
      if (cancel) return;

      var oldRCE = this.RaiseListChangedEvents;
      this.RaiseListChangedEvents = false;

      try
      {
        Child_Update(this);
      }
      finally
      {
        this.RaiseListChangedEvents = oldRCE;
      }
      OnUpdated();
    }
    partial void OnUpdating(ref bool cancel);
    partial void OnUpdated();
    #endregion //Data Access - Update
    #endregion //Data Access - Update

Appointment.cs
using System;
using System.Data;
using System.Data.SqlClient;
using Csla;
using Csla.Data;
using Csla.Security;
using Csla.Validation;

namespace MyLib.Scheduling
{
  [Serializable()]
  public partial class Appointment : Csla.BusinessBase<Appointment>
  {
    #region Business Properties and Methods
    private byte[] _tStamp;
    //register properties
    private static PropertyInfo<int> AppointmentIDProperty = RegisterProperty(new PropertyInfo<int>("AppointmentID", "Appointment ID"));
    private static PropertyInfo<string> SubjectProperty = RegisterProperty(new PropertyInfo<string>("Subject", "Subject"));

    [System.ComponentModel.DataObjectField(true, true)]
    public int AppointmentID
    {
      get { return GetProperty(AppointmentIDProperty); }
      private set { SetProperty(AppointmentIDProperty, value); }
    }

    public byte[] TimeStamp
    {
      get { return _tStamp; }
      private set { _tStamp = value; }
    }

    public string TStampString
    {
      get { return (_tStamp != null) ? BytesToString(_tStamp) : ""; }
    }

   
    public string Subject
    {
      get { return GetProperty(SubjectProperty); }
      set { SetProperty(SubjectProperty, value); }
    }

    #endregion //Business Properties and Methods

    #region Utilities
    private string BytesToString(byte[] bytes_Input) 
    { 
      string strTemp = ""; 
      for (int x = 0; x <= bytes_Input.GetUpperBound(0); x++) 
      { 
        int number = int.Parse(bytes_Input[x].ToString()); 
        strTemp += number.ToString("X").PadLeft(2, '0'); 
      } 
      return strTemp; 
    }
    #endregion

    #region Validation Rules
    protected override void AddBusinessRules()
    {
      // Subject
      ValidationRules.AddRule(CommonRules.StringMaxLength, new CommonRules.MaxLengthRuleArgs(SubjectProperty, 100));

      AddCustomRules();
    }

    partial void AddCustomRules();
    #endregion //Validation Rules

    #region Factory Methods
    public static Appointment NewAppointment()
    {
      return DataPortal.CreateChild<Appointment>();
    }

    public static Appointment GetAppointment(Appointment data)
    {
      return DataPortal.FetchChild<Appointment>(data);
    }

    public static Appointment GetAppointment(SafeDataReader dr)
    {
      return new Appointment(dr);
    }

    public static void DeleteAppointment(int appointmentID)
    {
      DataPortal.Delete(new SingleCriteria<Appointment, int>(appointmentID));
    }

    private Appointment()
    { /* require use of factory method */ }

    internal Appointment(SafeDataReader dr)
    {
      LoadProperty(AppointmentIDProperty, dr.GetInt32("AppointmentID"));
      _tStamp = (byte[])dr.GetValue("TStamp");
      LoadProperty(SubjectProperty, dr.GetString("Subject"));

      MarkOld();
    }

    #endregion //Factory Methods

    #region Data Access






    #region Data Access - Child_Create
    [RunLocal]
    protected override void Child_Create()
    {
      bool cancel = false;
      OnCreating(ref cancel);
      if (cancel) return;

      ValidationRules.CheckRules();

      OnCreated();
    }

    partial void OnCreating(ref bool cancel);
    partial void OnCreated();
    #endregion //Data Access - Create

    #region Data Access - Child_Fetch
    protected void Child_Fetch(Appointment data)
    {
      bool cancel = false;
      OnLoading(data, ref cancel);
      if (cancel) return;

      LoadProperty(AppointmentIDProperty, data.AppointmentID);
      _tStamp = data.TimeStamp;
      LoadProperty(SubjectProperty, data.Subject);

      ValidationRules.CheckRules();

      OnLoaded();
    }

    partial void OnLoading(Appointment data, ref bool cancel);
    partial void OnLoaded();
    #endregion //Data Access - Create

 #region Data Access - Child_Insert

    private void Child_Insert(AppointmentList parent)
    {
      bool cancel = false;
      OnInserting(parent, ref cancel);
      if (cancel) return;

      var data = new MyLib.Scheduling.Appointment();

      OnMemberReading(data);

      using (var mgr = ConnectionManager<SqlConnection>.GetManager("MyLib"))
      {
        using (var cmd = mgr.Connection.CreateCommand())
        {
          cmd.CommandType = System.Data.CommandType.StoredProcedure;
          cmd.CommandText = "dsSchedulingAppointments_Insert";

          SqlParameter parmA = new SqlParameter("@AppointmentID", SqlDbType.Int);
          parmA.Direction = ParameterDirection.Output;
          cmd.Parameters.Add(parmA);

          SqlParameter parmB = new SqlParameter("@NewTStamp", SqlDbType.Timestamp);
          parmB.Direction = ParameterDirection.Output;
          cmd.Parameters.Add(parmB);

          OnMemberReading(data);

          FillUpdateInsertAppointmentData(data);
          FillUpdateInsertCmdParams(cmd, data);

          OnMemberRead();

          cmd.ExecuteNonQuery();

          LoadProperty(AppointmentIDProperty, (int)parmA.Value);
          _tStamp = (byte[])parmB.Value;

          MarkOld();
          MarkClean();

        }//using
      }//using

      OnInserted();
    }

Form1.cs
    private void button1_Click(object sender, EventArgs e)
    {
      Appointment apt = fApptList.AddNew();
      apt.Subject = "Testing";

      apt.ApplyEdit();

      fApptList = fApptList.Save();
      gridMain.Refresh();
}

Thoughts?

RedShiftZ replied on Friday, May 08, 2009

Correction. fApptList is now getting updated. Just the grid is not.

RedShiftZ replied on Friday, May 08, 2009

Resetting the BindingSource's DataSource to fApptList seems to fix it. But this seems like a bad idea to me...

      fApptList = fApptList.Save();
      appointmentListBindingSource.DataSource = fApptList;

Another way?

rsbaker0 replied on Friday, May 08, 2009

RedShiftZ:
Resetting the BindingSource's DataSource to fApptList seems to fix it. But this seems like a bad idea to me...

      fApptList = fApptList.Save();
      appointmentListBindingSource.DataSource = fApptList;

Another way?

That's pretty much the way it is.

CSLA has some pretty firm requirements that (1) objects be unbound from their data source during a save, and (2) you have to rebind the returned object after the save.

It's possible that if this list were a child of a root object, that you might only have to rebind the root and the bindingsource for the child list might automatically refresh depending on how the binding was done, but you'll have to rebind at least one object to its data source after saving.

Also, with default behavior to implement IEditableObject in place, you can't call BeginEdit()/ApplyEdit()/UndoChanges() or any save operations while the object is bound. If you disable IEditableObject, then you can do some of these things but this is discouraged.

RedShiftZ replied on Monday, May 11, 2009

rsbaker0:

RedShiftZ:
Resetting the BindingSource's DataSource to fApptList seems to fix it. But this seems like a bad idea to me...

      fApptList = fApptList.Save();
      appointmentListBindingSource.DataSource = fApptList;

Another way?

That's pretty much the way it is.

CSLA has some pretty firm requirements that (1) objects be unbound from their data source during a save, and (2) you have to rebind the returned object after the save.



So does this apply to the EditableRootList as well. So when you move to the last item in a grid, then leave it you need to rebind?

rsbaker0 replied on Monday, May 11, 2009

No, but ERLB is doing the saving for you automatically when the EditLevel of each item goes to zero. I think this is a special use case that works because the standard paradigm is for the grid to call BeginEdit() when a row is entered and EndEdit() (or ApplyEdit -- don't have the code in front of me) when you leave the row.

Copyright (c) Marimer LLC