How do I get the framework to save this?

How do I get the framework to save this?

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


ataylorm posted on Tuesday, December 18, 2007

Alrighty, 

So here I am without sleep for 2 days and I still can't figure this out.  So I'm back to beg for a little help.

So I have a User class, and the user class has a sub class Address which is created as a list Addresses.  This allows User to have 1 or 50 different addresses on file.

Due to speed requirements for the system we are saving the addresses in the database as such

User Table:

GUID
TimeStamp

UserAddresses Table:

UserGUID
AddressGUID
FieldID
FieldValue
TimeStamp

So what happens here is that when they save an address, we get a timestamp (unix style 32 bit) which we then update user with, and instead of updating records in UserAddresses we insert all new records (for all fields, not just the edited ones) that have the updated timestamp and a clean up processes goes in and deletes the old records after 90 days (gives us a live history type as well as performance boost).

The problem I am having is that if say someone changes the street, and zipcode for an address, I want to only insert all new records once, not once for each edit.

Any ideas how I do this?  I can send a copy of the code if you need.

Thanks!

Andrew

 

ataylorm replied on Tuesday, December 18, 2007

Here is a look at the specific source code in question.

 

using System;
using System.Collections.Generic;
using Csla;
using Csla.Data;
using Csla.Validation;
using ProjectUnicorn.Library.Utilities;
using System.Data;
using UnicornData;
namespace ProjectUnicorn.Library
{
[
Serializable()]
public class User : Csla.BusinessBase<User>
{
#region Properties
//Do we need to have any comments in property declarations?
private string _UserID = "";
private string _UserName = "";
private string _Password = "";
private string _FirstName = "";
private string _LastName = "";
private string _EmailAddress = "";
private SmartDate _BirthDate;
private int _Rolls = 0;

#region
Get/Set

public
string UserID
{
     get { return _UserID; }
     set
    {
      _UserID =
value;
      PropertyHasChanged(
"UserID");
   }
}
public string UserName
{
get { return _UserName; }
set
{
_UserName =
value;
PropertyHasChanged(
"UserName");
}
}
public string Password
{
get { return _Password; }
set
{
_Password =
value;
PropertyHasChanged(
"Password");
}
}
public string FirstName
{
get { return _FirstName; }
set
{
_FirstName =
value;
PropertyHasChanged(
"FirstName");
}
}
public string LastName
{
get { return _LastName; }
set
{
_LastName =
value;
PropertyHasChanged(
"LastName");
}
}
public string EmailAddress
{
get { return _EmailAddress; }
set
{
_EmailAddress =
value;
PropertyHasChanged(
"EmailAddress");
}
}
public SmartDate BirthDate
{
get { return _BirthDate; }
set
{
_BirthDate =
value;
PropertyHasChanged(
"BirthDate");
}
}
public int Rolls
{
get { return _Rolls; }
set
{
_Rolls =
value;
PropertyHasChanged(
"Rolls");
}
}
 
#endregion
#endregion
#region
Subclasses
[
Serializable()]
public class UserAccount : User
{
#region Properties
private string _PhoneNumber = "";
private string _MobileNumber = "";
private List<Address> _addresses = new List<Address>();
#region Get/Set
public string PhoneNumber
{
get { return _PhoneNumber; }
set
{
_PhoneNumber =
value;
PropertyHasChanged(
"PhoneNumber");
}
}
public string MobileNumber
{
get { return _MobileNumber; }
set
{
_MobileNumber =
value;
PropertyHasChanged(
"MobileNumber");
}
}
public List<Address> Addresses
{
get { return _addresses; }
set
{
_addresses =
value;
PropertyHasChanged(
"Addresses");
}
}
public Address getDefaultAddress()
{
foreach (Address addr in _addresses)
{
if (addr.IsDefault)
return addr;
}
return null;
}
#endregion
#endregion
#region
Methods
 
#endregion
#region
Addresses
public class Address : UserAccount
{
public override string ToString()
{
string tmp = _address1;
if (_address2 != null)
{
tmp += (
"\r\n" + _address2);
}
if (_address3 != null)
{
tmp += (
"\r\n" + _address3);
} tmp += (
"\r\n" + _city);
tmp += (
", " + _state);
tmp += (
" " + _zipcode);
tmp += (
"\r\nIs Default: " + _isDefault);
return tmp;
}
#region Address Properties
private string _addressname;
private string _address1;
private string _address2;
private string _address3;
private string _city;
private string _state;
private string _zipcode;
private bool _isDefault = false;
 
public string AddressName
{
get { return _addressname; }
}
public string Address1
{
get { return _address1; }
set
{
_address1 =
value;
PropertyHasChanged(
"Address1");
}
}
public string Address2
{
get { return _address2; }
set
{
_address2 =
value;
PropertyHasChanged(
"Address2");
}
}
public string Address3
{
get { return _address3; }
set
{
_address3 =
value;
PropertyHasChanged(
"Address3");
}
}
public string City
{
get { return _city; }
set
{
_city =
value;
PropertyHasChanged(
"City");
}
}
public string State
{
get { return _state; }
set
{
_state =
value;
PropertyHasChanged(
"State");
}
}
public string Zipcode
{
get { return _zipcode; }
set
{
_zipcode =
value;
PropertyHasChanged(
"Zipcode");
}
}
public bool IsDefault
{
get { return _isDefault; }
set
{
_isDefault =
value;
PropertyHasChanged(
"IsDefaultAddress");
}
}
#endregion Address Properties
}
 
#endregion Addresses
}
 
#region Business Methods
/*
* The Business Methods region will contain the methods that are used by
* UI code (or other client-level code) to interact with the business
* object. This includes any properties that allow retrieval or changing
* of values in the object, as well as methods that operate on the object's
* data to perform business processing.
*/
private int _id = 0;
private byte[] _timestamp = new byteMusic [8];
[System.ComponentModel.DataObjectField(
true, true)]
public int Id
{
[System.Runtime.CompilerServices.
MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
get
{
CanReadProperty(
true);
return _id;
}
}
public override bool IsValid
{
get { return base.IsValid; }
// If this object contains child objects, then:
// get { return base.IsValid && [child object].IsValid; }
}
public override bool IsDirty
{
get { return base.IsDirty; }
// If this object contains child objects, then:
// get { return base.IsDirty || _assignments.IsDirty; }
}
protected override object GetIdValue()
{
return _id;
}
public bool CreateAccount(string strUserName, string strPassword, string strEmailAddress, SmartDate dtDateOfBirth, int intRoles)
{
return true;
}
public bool CheckUsernameAvailability(string strUserName)
{
DataView tmpReturnedData;
DataTools.UserData tmpData =
new DataTools.UserData();
tmpReturnedData =
new DataView();
tmpReturnedData.Table = tmpData.GetUserByUserName(strUserName).Tables[
"users"];
if (tmpReturnedData.Table.Rows.Count == 0)
{
return true;
}
else
{
return false;
}
}
public bool CheckEmailAddressAvailability(string strEmailAddress)
{
DataView tmpReturnedData;
DataTools.UserData tmpData =
new DataTools.UserData();
tmpReturnedData =
new DataView();
tmpReturnedData.Table = tmpData.GetUserByEmailAddress(strEmailAddress).Tables[
"users"];
if (tmpReturnedData.Table.Rows.Count == 0)
{
return true;
}
else
{
return false;
}
}
 
public void DisableAccount(long UserID, string strReason)
{
}
public bool UpdateAccount()
{
 
return true;
}
 
#endregion
#region
Validation Rules
/*
* The Validation Rules region will contain the AddBusinessRules() method, and
* any custom rule methods required by the object.
*/
protected override void AddBusinessRules()
{
 
ValidationRules.AddRule(
new RuleHandler(CommonRules.StringMaxLength),
new CommonRules.MaxLengthRuleArgs("Address1", 50));
ValidationRules.AddRule(
new RuleHandler(CommonRules.StringMaxLength),
new CommonRules.MaxLengthRuleArgs("Address2", 50));
ValidationRules.AddRule(
new RuleHandler(CommonRules.StringMaxLength),
new CommonRules.MaxLengthRuleArgs("Address3", 50));
ValidationRules.AddRule(
new RuleHandler(CommonRules.StringMaxLength),
new CommonRules.MaxLengthRuleArgs("City", 30));
ValidationRules.AddRule(
new RuleHandler(CommonRules.StringMaxLength),
new CommonRules.MaxLengthRuleArgs("State", 30));
ValidationRules.AddRule(
new RuleHandler(CommonRules.StringMaxLength),
new CommonRules.MaxLengthRuleArgs("Zipcode", 10));
 
ValidationRules.AddRule(
new RuleHandler(CommonRules.StringMaxLength),
new CommonRules.MaxLengthRuleArgs("FirstName", 30));
ValidationRules.AddRule(
new RuleHandler(CommonRules.StringMaxLength),
new CommonRules.MaxLengthRuleArgs("LastName", 30));
ValidationRules.AddRule(
new RuleHandler(CommonRules.StringMaxLength),
new CommonRules.MaxLengthRuleArgs("EmailAddress", 100));
}
#endregion
#region
Authorization Rules
/*
* The Authorization Rules region will contain the AddAuthorizationRules() method.
* It will also contain a standard set of static methods indicating whether the
* current user is authorizaed to get, add, save, or delete this type of business
* object.
*/
protected override void AddAuthorizationRules()
{
// AuthorizationRules.AllowWrite();
}
public static bool CanAddObject()
{
bool result = false;
return result;
}
public static bool CanGetObject()
{
bool result = true;
return result;
}
public static bool CanDeleteObject()
{
bool result = false;
return result;
}
public static bool CanEditObject()
{
bool result = false;
return result;
}
#endregion
#region
Factory Methods
/*
* The Factory Methods region will contain the static factory methods to create or
* retrieve the object, along with the static delete method (if the object is
* editable. It will also contain the default constructor for the class, which must
* be scoped as non-public (i.e. private or protected) to force the use of the factory
* methods when creating the business object.
*/
public static User NewUser()
{
if (!CanAddObject())
throw new System.Security.SecurityException(
"User not authorized to add a user");
return DataPortal.Create<User>();
}
public static void DeleteUser(int id)
{
if (!CanDeleteObject())
throw new System.Security.SecurityException(
"User not authorized to remove a user");
DataPortal.Delete(
new Criteria(id));
}
public static User GetUser(int id)
{
if (!CanGetObject())
throw new System.Security.SecurityException(
"User not authorized to view a user");
return DataPortal.Fetch<User>(new Criteria(id));
}
public override User Save()
{
if (IsDeleted && !CanDeleteObject())
throw new System.Security.SecurityException(
"User not authorized to remove a user");
else if (IsNew && !CanAddObject())
throw new System.Security.SecurityException(
"User not authorized to add a user");
else if (!CanEditObject())
throw new System.Security.SecurityException(
"User not authorized to update a user");
return base.Save();
}
protected User()
{
}
#endregion
#region
Data Access
/*
* The Data Access region will contain the DataPortal_* methods. It will also contain
* the Criteria class used to create, retrieve, or delete the object.
*/
[Serializable()]
private class Criteria
{
private int _id;
public int Id
{
get { return _id; }
}
public Criteria(int id)
{ _id = id; }
}
// Use [RunLocal()] for objects that do not need to get populated with default data from the DB
[RunLocal()]
protected override void DataPortal_Create()
{
// nothing to initialize
ValidationRules.CheckRules();
}
private void DataPortal_Fetch(Criteria criteria)
{
}
protected override void DataPortal_Insert()
{
}
protected override void DataPortal_Update()
{
}
protected override void DataPortal_DeleteSelf()
{
DataPortal_Delete(
new Criteria(_id));
}
private void DataPortal_Delete(Criteria criteria)
{
}
protected override void OnDeserialized(System.Runtime.Serialization.StreamingContext context)
{
}
 
#endregion
#region
Exists
/*
* The Exists region will contain static methods to determine whether a specific instance
* of the business object exists.
*/
public static bool Exists(string id)
{
return ExistsCommand.Exists(id);
}
[
Serializable()]
private class ExistsCommand : CommandBase
{
private string _id;
private bool _exists;
public bool ResourceExists
{
get { return _exists; }
}
public static bool Exists(string id)
{
ExistsCommand result;
result = DataPortal.Execute<ExistsCommand>(
new ExistsCommand(id));
return result.ResourceExists;
}
private ExistsCommand(string id)
{
_id = id;
}
protected override void DataPortal_Execute()
{
_exists =
false;
}
}
#endregion
}
}

ajj3085 replied on Tuesday, December 18, 2007

Your design seems off to me.  Address inherits UserAccount?  A physical street address should be have like a user's login account?  Don't inherit to get properties, inherit from subclasses to get behavior (methods), and even then you need to be careful about it.

Read the chapter in the book about designing the project tracker library.  That will help guide you on how to build your BOs.

ataylorm replied on Tuesday, December 18, 2007

Did that because it was the only way we could find to get the PropertyHasChanged to work and was suggested by other users on this board.

ajj3085 replied on Wednesday, December 19, 2007

PropertyHasChanged doesn't require all objects inhert from each other.  I'd be suprised if that was the recommended design. 

I'd recommend something like this:

[Serializable]
public sealed class UserAccount : BusinessBase<UserAccount> {
    public UserAddresses Addresses { get; } // Be sure to listen to ListChanged events in UserAccount
}

[Serializable]
public sealed class UserAddresses : BusinessListBase<UserAddresses, UserAddress> {
    private UserAddresses() { MarkAsChild(); // this is important }
}

[Serializable]
public sealed class UserAddress : BusinessBase<UserAddress> {
    private UserAddress() { MarkAsChild(); // this is important }
}

Of course fill in the relevent properties and fields.  The pattern you should follow for properties is this:

public string AddressLine1 {
    get {
        string result;
 
        if ( CanReadProperty( "AddressLine1" ) ) {
            result = addressLine1;
        }
        else {
            result = "";
        }

        return result;
    }
    set {
         CanWriteProperty( "AddressLine1", true );
         if ( addressLine1 != value ) {
             addressLine1 = value;
             PropertyHasChanged( "AddressLine1" );
         }
    }
}

HTH
Andy

Copyright (c) Marimer LLC