using System;
using System.Collections;
using System.Data;
namespace ODX.Core{
/// <summary>
/// The base class to create persistable objects.
/// </summary>
/// <remarks>
/// <example>
/// <code>
/// <![CDATA[
/// [Table]
/// public class Person : Entity
/// {
/// public abstract string Name { get; set; }
/// public abstract DateTime BirthDate { get; set; }
/// }
/// class Program
/// {
/// static void Main()
/// {
/// Session s = new Sessoin(
/// new XmlDataProvider("persons.xml"),
/// Assembly.GetExecutingAssembly());
///
/// Person p = session.Create<Person>();
///
/// p.Name = "John";
/// p.BirthDate = DateTime.Now;
///
/// session.Save();
/// }
/// }
/// ]]>
/// </code>
/// Now explore <c>persons.xml</c> and see your data saved.
/// </example>
/// </remarks>
public class Entity : ICloneable
{
private RowList rows;
private static string wrappingID = null;
private static Session wrappingSession = null;
private string id;
///<summary>
/// Identifier of the entity.
///</summary>
/// <remarks>
/// ODX uses GUIDs to identify entities. But as far as GUID itself is not an SQL-92 type
/// ODX converts it to the 22-char base64-encoded string: <code>Convert.ToBase64String(Guid.NewGuid().ToByteArray())</code>
/// </remarks>
public string ID { get { return id; } }
/// <summary>
/// Session which is the entity belongs to.
/// </summary>
public Session Session { get { return Session.GetSession(rows.Any); } }
internal DataRow[] Rows { get { return rows.All; } }
/// <summary>
/// Normally, you do not call this constructor but define an abstract subclass and perform automatic proxy build.
/// </summary>
public Entity()
{
if (wrappingID != null)
{
Wrap(wrappingID);
return;
}
Hashtable dataRows = new Hashtable(StringComparer.InvariantCultureIgnoreCase);
string[] tables = wrappingSession.Pm.GetTypeTables(GetType());
if (tables.Length == 0)
throw new OdxException("Table attribute not set!!!!!!!!");
id = Session.CreateID();
foreach (string tableName in tables)
{
DataTable dt = wrappingSession.Schema.Tables[tableName];
DataRow dr = dt.NewRow();
dr["ID"] = id;
dt.Rows.Add(dr);
dataRows[tableName] = dr;
}
rows = new RowList(dataRows);
Type type = GetType();
string typeTable = wrappingSession.Pm.GetTypeTable(type);
string typeDef = Polymorpher.GetTypeDef(type);
while (typeTable != null)
{
typeDef = Polymorpher.GetTypeDef(type) ?? typeDef;
DataRow row = GetRow(typeTable);
if (typeDef != null &&
row.Table.Columns.Contains(Session.TypeDefField) &&
row[Session.TypeDefField] is DBNull)
{
row[Session.TypeDefField] = typeDef;
}
type = type.BaseType;
typeTable = wrappingSession.Pm.GetTypeTable(type);
}
wrappingSession.RegisterEntity(this);
OnCreated();
}
private void Wrap(string entityID)
{
string[] tables = wrappingSession.Pm.GetTypeTables(GetType());
if (tables.Length == 0)
throw new OdxException("Table attribute not set!!!!!!!!");
id = entityID;
Hashtable dataRows = new Hashtable(StringComparer.InvariantCultureIgnoreCase);
foreach (string tableName in tables)
{
DataRow dr = wrappingSession.FindRecord(tableName, entityID);
dataRows[tableName] = dr;
}
rows = new RowList(dataRows);
wrappingSession.RegisterEntity(this);
}
internal static Entity CreateWrapper(Session session, Type type, string ID)
{
Entity e;
if (ID != null && (e = session.FindEntity(ID)) != null)
return e;
lock (typeof(Entity))
{
try
{
wrappingID = ID;
wrappingSession = session;
return (Entity)Activator.CreateInstance(type);
}
finally { wrappingID = null; wrappingSession = null; }
}
}
internal static Entity CreateWrapper(DataRow row)
{
string ID = row["ID"].ToString();
Session session = Session.GetSession(row);
Entity e = session.FindEntity(ID);
if (e != null)
return e;
Type type = session.Pm.DefineType(row);
return CreateWrapper(session, type, ID);
}
internal void FixupReferences()
{
foreach (DataRow dr in new ArrayList(rows))
{
if (dr.RowState == DataRowState.Detached || dr.RowState == DataRowState.Deleted)
{
DataRow validRow = dr.Table.Rows.Find(id);
if (validRow != null)
rows[dr.Table.TableName] = validRow;
}
}
}
internal DataRow GetRow(string tableName)
{
return rows[tableName];
}
protected Entity GetParent(string dataRelation)
{
return GetParent(Session.Schema.Relations[dataRelation]);
}
protected Entity GetParent(DataRelation dataRelation)
{
DataRow r = GetRow(dataRelation.ChildTable.TableName);
object parentID = r[dataRelation.ChildColumns[0].ColumnName];
if (parentID is DBNull)
return null;
DataRow parent = Session.FindRecord(dataRelation.ParentTable.TableName, parentID.ToString());
if (parent != null)
return CreateWrapper(parent);
return null;
}
protected void SetParent(string dataRelation, Entity e)
{
SetParent(Session.Schema.Relations[dataRelation], e);
}
protected void SetParent(DataRelation dataRelation, Entity e)
{
DataRow r = GetRow(dataRelation.ChildTable.TableName);
if (e == null)
r[dataRelation.ChildColumns[0].ColumnName] = DBNull.Value;
else
r[dataRelation.ChildColumns[0].ColumnName] = e.ID;
}
/// <summary>
/// Returns wheather the entity was not saved to the data source.
/// </summary>
public bool IsNew
{
get
{
foreach (DataRow dr in rows)
if (dr.RowState != DataRowState.Added)
return false;
return true;
}
}
/// <summary>
/// Determines whether object has changet persistent properties.
/// </summary>
public bool IsChanged
{
get
{
foreach (DataRow dr in rows)
if (dr.RowState != DataRowState.Unchanged)
return true;
return false;
}
}
/// <summary>
/// Determines whether object is marked to be deleted on Session.Save()
/// </summary>
public bool IsDeleted
{
get
{
foreach (DataRow dr in rows)
if (dr.RowState == DataRowState.Deleted || dr.RowState == DataRowState.Detached)
return true;
return false;
}
}
/// <summary>
/// Marks object for deletion
/// </summary>
public void Delete()
{
foreach (DataRow dr in rows)
dr.Delete();
}
protected internal void SetProperty(string tableName, string propertyName, object value)
{
GetRow(tableName)[propertyName] = value;
}
protected internal object GetProperty(string tableName, string propertyName, DataRowVersion version)
{
return GetRow(tableName)[propertyName, version];
}
protected internal object GetProperty(string tableName, string propertyName)
{
return GetProperty(tableName, propertyName, DataRowVersion.Default);
}
protected internal void SetProperty(string propertyName, object value)
{
bool found = false;
foreach (DataRow row in rows)
{
if (row.Table.Columns.Contains(propertyName))
{
row[propertyName] = value;
found = true;
}
}
if (!found)
throw new OdxException("Wrong field name specified!!!");
}
protected internal object GetProperty(string propertyName, DataRowVersion version)
{
foreach (DataRow row in rows)
if (row.Table.Columns.Contains(propertyName))
return row[propertyName, version];
throw new OdxException("Wrong field name specified!!!");
}
protected internal object GetProperty(string propertyName)
{
return GetProperty(propertyName, DataRowVersion.Default);
}
protected internal virtual void OnCreated() { }
protected internal virtual void OnChanged(DataColumnChangeEventArgs e) { }
protected internal virtual void OnChanging(DataColumnChangeEventArgs e) { }
protected internal virtual void OnDeleted(DataRowChangeEventArgs e) { }
protected internal virtual void OnDeleting(DataRowChangeEventArgs e) { }
/// <summary>
/// Undoes changes made after the last Session.Save()
/// </summary>
public void RejectChanges()
{
foreach (DataRow row in rows)
row.RejectChanges();
}
internal void Refresh()
{
rows.Refresh();
}
object ICloneable.Clone()
{
return Clone();
}
/// <summary>
/// Creates new object with new ID and copies other properties into new object.
/// </summary>
/// <returns></returns>
public Entity Clone()
{
string newID = Session.CreateID();
foreach (DataRow dr in rows)
{
DataRow newDR = dr.Table.NewRow();
foreach (DataColumn dc in dr.Table.Columns)
{
if (dr[dc] is ICloneable)
newDR[dc] = ((ICloneable)dr[dc]).Clone();
else
newDR[dc] = dr[dc];
}
newDR["ID"] = newID;
dr.Table.Rows.Add(newDR);
}
return CreateWrapper(Session, GetType(), newID);
}
internal void Impersonate(Session tmp, string newID)
{
lock (typeof(Entity))
{
wrappingSession = tmp;
Wrap(newID);
}
}
}
}
|