// Persist library : Persistence layer
// Copyright (C) 2003 Vincent Daron
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Data;
using System.Threading;
using System.Collections;
using Persist.Config;
using Persist.Criteria;
using Persist.Converters;
using Persist.Statements;
using Persist.Maps;
using Persist.Sql;
namespace Persist{
/// <summary>
/// This class is used to retreive/save and delete objects.
/// ... ... ...
/// </summary>
public class PersistenceManagerFactory : IDisposable
{
private Hashtable classMaps = new Hashtable();
private Hashtable databases = new Hashtable();
private Hashtable converters = new Hashtable();
private LocalDataStoreSlot theConnectionManager;
private static Configuration theConfiguration = null;
private static bool isLocked = false;
#region Singleton Implementation
private static PersistenceManagerFactory theInstance;
private PersistenceManagerFactory()
{
theConnectionManager = Thread.AllocateDataSlot();
theConfiguration = new Configuration();
}
/// <summary>
/// The Unique Instance of the PersistenceManagerFactory
/// </summary>
public static PersistenceManagerFactory Instance
{
get
{
lock(typeof(PersistenceManagerFactory))
{
if(theInstance == null)
{
theInstance = new PersistenceManagerFactory();
}
return theInstance;
}
}
}
#endregion
/// <summary>
/// Configure and Initialize the PersistenceManager
/// </summary>
/// <param name="configurationStore">a configuration store to read the configuration from</param>
public void Configure(IConfigurationStore configurationStore)
{
lock(typeof(PersistenceManagerFactory))
{
if(isLocked)
{
throw new Exception("Can't load config after a PersistenceManager was returned");
}
configurationStore.LoadConfiguration(theConfiguration);
}
}
public void ConfigureDatabase(string databaseName, string databaseType, System.Collections.Specialized.NameValueCollection parameters)
{
lock(typeof(PersistenceManagerFactory))
{
if(isLocked)
{
throw new Exception("Can't load config after a PersistenceManager was returned");
}
Configuration.AddRelationalDatabase(databaseName,databaseType,parameters);
}
}
public void Initialize()
{
lock(typeof(PersistenceManagerFactory))
{
if(isLocked)
{
throw new Exception("Can't load config after a PersistenceManager was returned");
}
theConfiguration.Init();
}
}
internal Configuration Configuration
{
get{return theConfiguration;}
}
/// <summary>
/// Return the Connection manager of the current thread.
/// The connection manager are'nt thread safe (due to Transactions, ...) and are therefore stored in the Thread object
/// </summary>
internal ConnectionManager ConnectionManager
{
get
{
isLocked = true;
ConnectionManager cm = (ConnectionManager)Thread.GetData(theConnectionManager);
if(cm == null)
{
System.Diagnostics.Debug.WriteLine("Creating new Connection Manager");
cm = new ConnectionManager();
Thread.SetData(theConnectionManager,cm);
}
return cm;
}
}
#region IDisposable Implementation
/// <summary>
/// Destructor
/// </summary>
~PersistenceManagerFactory()
{
Dispose(false);
}
/// <summary>
/// Dispose the PersistenceManager Factory
/// </summary>
public void Dispose()
{
Dispose(true);
}
/// <summary>
/// Dispose Pattern
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
if(disposing)
{
foreach(RelationalDatabase database in databases.Values)
{
database.Dispose();
}
GC.SuppressFinalize(this);
}
}
#endregion
/// <summary>
/// Load an object from the database
/// </summary>
/// <param name="obj">the object to filled with database values</param>
public void RetrieveObject(PersistentObject obj)
{
ClassMap classMap = theConfiguration.GetClassMap(obj);
IDbConnection conn = ConnectionManager.GetConnection(classMap.RelationalDatabase);
try
{
RetrieveObject(obj, classMap, conn, false);
}
finally
{
ConnectionManager.FreeConnection(classMap.RelationalDatabase, conn);
}
}
/// <summary>
/// Load an object as a proxy. a Proxy object do not have all it's attributes loaded at startup.
/// This is mainly usefull for big objects with large BLOB fields and so on ...
/// The fields to retrieve when the object is retrieved as proxy are specified in configuration. By default, only the primary key is retrieved
/// </summary>
/// <param name="obj">the object to filled with database values</param>
public void RetrieveObjectAsProxy(PersistentObject obj)
{
ClassMap classMap = theConfiguration.GetClassMap(obj);
IDbConnection conn = ConnectionManager.GetConnection(classMap.RelationalDatabase);
try
{
RetrieveObjectAsProxy(obj, classMap, conn, false);
}
finally
{
ConnectionManager.FreeConnection(classMap.RelationalDatabase, conn);
}
}
/// <summary>
/// Delete the sepcified object from the database
/// This method delete also all the objects associated where the "DeleteAutomatic" flag is specified.
/// </summary>
/// <param name="obj">the object to delete</param>
public void DeleteObject(PersistentObject obj)
{
ClassMap classMap = theConfiguration.GetClassMap(obj);
IDbConnection conn = ConnectionManager.GetConnection(classMap.RelationalDatabase);
try
{
DeleteObject(obj, classMap, conn);
}
finally
{
ConnectionManager.FreeConnection(classMap.RelationalDatabase, conn);
}
}
/// <summary>
/// Save the object to Database.
/// The object must have changed to be updated (IsDirty flag).
/// This method save also all the objects associated where the "SaveAutomatic" flag is specified.
/// </summary>
/// <param name="obj"></param>
public void SaveObject(PersistentObject obj)
{
ClassMap classMap = theConfiguration.GetClassMap(obj);
IDbConnection conn = null;
try
{
conn = ConnectionManager.GetConnection(classMap.RelationalDatabase);
SaveObject(obj, classMap, conn);
}
finally
{
if(conn != null)
{
ConnectionManager.FreeConnection(classMap.RelationalDatabase, conn);
}
}
}
/// <summary>
/// Retrieve/Reload One Assocation of an object
/// </summary>
/// <param name="obj">the object to retrieve assocation from</param>
/// <param name="targetName">the Attribute Target of the assocation to retrieve</param>
/// <param name="orderAttributes">the Order of the association</param>
internal void RetrieveAssociation(PersistentObject obj, string targetName, Order[] orderAttributes)
{
if(obj.isPersistent)
{
ClassMap classMap = theConfiguration.GetClassMap(obj);
UniDirectionalAssociationMap aMap = classMap.GetAssociationMap(targetName);
if(aMap == null)
{
throw new Exception("No Association for target "+targetName+" found in class " + classMap.Name);
}
//IDbConnection conn = ConnectionManager.GetConnection( aMap.ForClass.RelationalDatabase );
try
{
RetrieveAssociation(obj, targetName, orderAttributes, classMap/*, conn*/);
}
finally
{
// Do not Free Connection Because it's already closed by Cursor
//FreeConnection(classMap.RelationalDatabase, conn);
}
}
}
/// <summary>
/// Start a Transaction
/// <seealso cref="Persist.Attributes.TransactionAttribute"/>
/// </summary>
public void BeginTransaction()
{
ConnectionManager.BeginTransaction();
}
/// <summary>
/// Commit the Transaction
/// </summary>
public void Commit()
{
ConnectionManager.Commit();
}
/// <summary>
/// Rollback the transaction
/// </summary>
public void Rollback()
{
ConnectionManager.Rollback();
}
internal void RetrieveAssociations(PersistentObject obj, ClassMap classMap/*, IDbConnection conn*/)
{
if(classMap.SuperClass != null)
{
// Retrieve associated data for superclass
RetrieveAssociations(obj, classMap.SuperClass/*, conn*/);
}
// Retrieve associated data
foreach(UniDirectionalAssociationMap aMap in classMap.AssociationMaps.Values)
{
if(!aMap.IsRetrieveAutomatic)
{
continue;
}
else
{
RetrieveAssociation(obj, aMap,/*aMap.OrderAttributes*/null, classMap/*, conn*/);
}
}
}
private void RetrieveAssociation(PersistentObject obj, String targetName, Order[] orderAttributes, ClassMap classMap/*, IDbConnection conn*/)
{
UniDirectionalAssociationMap aMap = classMap.GetAssociationMap(targetName);
if(aMap == null)
throw new Exception("Association name for target " + targetName + " not found");
if(aMap.Target == null)
throw new Exception("Target attribute with name " + targetName + " not found");
RetrieveAssociation(obj, aMap, orderAttributes, classMap/*, conn*/);
}
private void RetrieveAssociation(PersistentObject obj, UniDirectionalAssociationMap aMap, Order[] orderAttributes, ClassMap classMap/*, IDbConnection conn*/)
{
RetrieveStatement criteria = aMap.GetStatement(orderAttributes);
ICollection criteriaParameters = aMap.GetStatementParameters(obj);
using (Cursor cursor = ProcessStatement(criteria, criteriaParameters,/* conn,*/ false))
{
if(aMap.Cardinality == UniDirectionalAssociationMap.CardinalityEnum.ONE_TO_ONE)
{
PersistentObject value = null;
IEnumerator cursorEnum = cursor.GetEnumerator();
if(cursorEnum.MoveNext())
{
value = (PersistentObject)cursorEnum.Current;
}
aMap.Target.SetValue(obj, value);
}
else if(aMap.Cardinality == UniDirectionalAssociationMap.CardinalityEnum.ONE_TO_MANY)
{
IList objects = (IList)aMap.Target.GetValue(obj);
// Clear collection if it exists
if(objects == null)
{
throw new Exception("the IList "+ aMap.TargetName+" of the 1..* association is null !");
}
objects.Clear();
foreach(object item in cursor)
{
objects.Add(item);
}
}
}
}
private void SaveObject(PersistentObject obj, ClassMap classMap, IDbConnection conn)
{
lock(obj)
{
if(obj.isSaving)
{
return;
}
obj.isSaving = true;
}
SqlStatement statement = null;
IDataReader aDataReader = null;
if(obj.isDirty)
{
// Check if object is in optimistic lock
// and check whether it was modified
if(obj.isOptimisticLock)
{
// Retrieve timestamp for this object and lock rows for this object
using(statement = classMap.GetSelectTimestampSqlFor(obj))
{
aDataReader = ConnectionManager.ExecuteStatementQuery(statement, conn);
}
if(aDataReader.Read())
{
DateTime optimistLockDateTime = (DateTime)classMap.OptimistLockAttributeMap.GetValue(obj);
if((optimistLockDateTime == DateTime.MinValue) ||
(!optimistLockDateTime.Equals(aDataReader.GetDateTime(0))))
{
aDataReader.Dispose();
throw new DBConcurrencyException("Object is marked for optimistic lock and was modified");
}
}
// Free DataReader resources
aDataReader.Dispose();
}
if(classMap.SuperClass != null)
{
// Store superclass
// Save previous value of isPersistent property
// This property should not change its value when superclasses are saved
bool isPersistent = obj.isPersistent;
SaveObject(obj, classMap.SuperClass, conn);
obj.isPersistent = isPersistent;
}
}
// Store associated data with straight association maps
for(int j = 0; j < classMap.StraightAssociationMapCount; j++)
{
UniDirectionalAssociationMap aMap = classMap.GetStraightAssociationMap(j);
if(!aMap.IsSaveAutomatic)
continue;
if(aMap.Cardinality == UniDirectionalAssociationMap.CardinalityEnum.ONE_TO_ONE)
{
PersistentObject value = (PersistentObject)aMap.Target.GetValue(obj);
if(value != null)
{
SaveObject(value, aMap.ForClass, conn);
for(int i = 0; i < aMap.EntriesCount; i++)
{
aMap.GetEntry(i).From.SetValue(obj,aMap.GetEntry(i).To.GetValue(value));
}
}
}
else if(aMap.Cardinality == UniDirectionalAssociationMap.CardinalityEnum.ONE_TO_MANY)
{
IList collection = (IList)aMap.Target.GetValue(obj);
if(collection != null)
{
foreach(PersistentObject persistentObject in collection)
{
SaveObject(persistentObject, aMap.ForClass, conn);
for(int i = 0; i < aMap.EntriesCount; i++)
{
aMap.GetEntry(i).From.SetValue(persistentObject,aMap.GetEntry(i).To.GetValue(obj));
}
}
}
}
}
if(obj.isDirty)
{
// Update TimeStamp Date
if(classMap.OptimistLockAttributeMap != null)
{
classMap.OptimistLockAttributeMap.SetValue(obj,DateTime.Now);
}
}
if(obj.isPersistent)
{
if(obj.isDirty)
{
// Update existent entry in the database
using(statement = classMap.GetUpdateSqlFor(obj))
{
ConnectionManager.ExecuteStatementNonQuery(statement, conn);
}
}
}
else
{
// Insert new entry into the database
for(int i = 0; i < classMap.PrimaryKeyAttributeCount; i++)
{
AttributeMap keyAttribute = classMap.GetKeyAttributeMap(i);
if(keyAttribute.ColumnMap.KeyType != ColumnMap.KeyTypeEnum.Primary ||
keyAttribute.ColumnMap.IdGenerator == null)
continue;
else
{
// Generate new ID for this attribute
keyAttribute.SetValue(obj, keyAttribute.ColumnMap.IdGenerator.GetNewId(classMap));
}
}
using(statement = classMap.GetInsertSqlFor(obj))
{
ConnectionManager.ExecuteStatementNonQuery(statement, conn);
}
}
// Store associated data with inverse association maps
for(int j = 0; j < classMap.InverseAssociationMapCount; j++)
{
UniDirectionalAssociationMap aMap = classMap.GetInverseAssociationMap(j);
if(!aMap.IsSaveAutomatic)
continue;
if(aMap.Cardinality == UniDirectionalAssociationMap.CardinalityEnum.ONE_TO_ONE)
{
PersistentObject value = (PersistentObject)aMap.Target.GetValue(obj);
if(value != null)
{
for(int i = 0; i < aMap.EntriesCount; i++)
{
aMap.GetEntry(i).From.SetValue(value,aMap.GetEntry(i).To.GetValue(obj));
}
SaveObject(value, aMap.ForClass, conn);
}
}
else if(aMap.Cardinality == UniDirectionalAssociationMap.CardinalityEnum.ONE_TO_MANY)
{
IList collection = (IList)aMap.Target.GetValue(obj);
if(collection != null)
{
foreach(PersistentObject pObj in collection)
{
for(int i = 0; i < aMap.EntriesCount; i++)
{
aMap.GetEntry(i).From.SetValue(pObj,aMap.GetEntry(i).To.GetValue(obj));
}
SaveObject(pObj, aMap.ForClass, conn);
}
}
}
}
obj.isPersistent = true;
obj.isDirty = false;
obj.isSaving = false;
}
private void DeleteObject(PersistentObject obj, ClassMap classMap, IDbConnection conn)
{
// Retrieve and delete associated data
foreach(UniDirectionalAssociationMap aMap in classMap.AssociationMaps.Values)
{
if(!aMap.IsDeleteAutomatic)
continue;
// If the relational database is the same, use the same connection
if(classMap.RelationalDatabase.Name == aMap.ForClass.RelationalDatabase.Name)
{
DeleteStatement deleteStatement = new DeleteStatement(aMap.GetStatement(null));
ICollection deleteStatementParameters = aMap.GetStatementParameters(obj);
ProcessStatementNonQuery(deleteStatement,deleteStatementParameters,conn);
}
else // otherwize Create a new Statement
{
DeleteStatement deleteStatement = new DeleteStatement(aMap.GetStatement(null));
ICollection deleteStatementParameters = aMap.GetStatementParameters(obj);
ProcessStatementNonQuery(deleteStatement,deleteStatementParameters);
}
}
// Delete object
using(SqlStatement statement = classMap.GetDeleteSqlFor(obj))
{
ConnectionManager.ExecuteStatementNonQuery(statement, conn);
}
if(classMap.SuperClass != null)
{
// Delete superclass
DeleteObject(obj, classMap.SuperClass, conn);
}
obj.isPersistent = false;
}
private void RetrieveObject(PersistentObject obj, ClassMap classMap, IDbConnection conn, bool isLock)
{
SqlStatement aStatement = null;
IDataReader aDataReader = null;
using(aStatement = classMap.GetSelectSqlFor(obj))
{
// Add FOR UPDATE clause if object needs to be locked
if(isLock)
{
aStatement.AddSqlClause(" " + classMap.RelationalDatabase.ClauseStringForUpdate);
}
using(aDataReader = ConnectionManager.ExecuteStatementQuery(aStatement, conn))
{
if(aDataReader.Read())
{
classMap.RetrieveObject(obj, aDataReader);
}
}
}
if(obj.isPersistent)
{
// Retrieve associations after object
RetrieveAssociations(obj, classMap/*, conn*/);
obj.isDirty = false;
}
}
private void RetrieveObjectAsProxy(PersistentObject obj, ClassMap classMap, IDbConnection conn, bool isLock)
{
// Check if object is in optimistic lock
// and get updated timestamp
SqlStatement statement = null;
IDataReader aDataReader = null;
using(statement = classMap.GetSelectProxySqlFor(obj))
{
// Add FOR UPDATE clause if object needs to be locked
if(isLock)
{
statement.AddSqlClause(" " + classMap.RelationalDatabase.ClauseStringForUpdate);
}
aDataReader = ConnectionManager.ExecuteStatementQuery(statement, conn);
}
if(aDataReader.Read())
{
classMap.RetrieveProxyObject(obj,aDataReader);
}
// Free DataReader
aDataReader.Dispose();
// Retrieve associations after object
RetrieveAssociations(obj, classMap/*, conn*/);
obj.isDirty = false;
}
#region Internal Statement Processing Methods
internal int ProcessStatementNonQuery(Statements.CountStatement statement, ICollection parameters)
{
IDbConnection conn = ConnectionManager.GetConnection(statement.ClassMap.RelationalDatabase);
try
{
int result = 0;
using(SqlStatement sqlStatement = statement.GetSqlStatement(parameters))
{
using(IDataReader dataReader = ConnectionManager.ExecuteStatementQuery(sqlStatement, conn))
{
if(dataReader.Read())
{
result = dataReader.GetInt32(0);
}
}
}
return result;
}
finally
{
ConnectionManager.FreeConnection(statement.ClassMap.RelationalDatabase, conn);
}
}
internal int ProcessStatementNonQuery(Statements.DeleteStatement statement, ICollection parameters)
{
IDbConnection conn = ConnectionManager.GetConnection(statement.ClassMap.RelationalDatabase);
try
{
return ProcessStatementNonQuery(statement,parameters,conn);
}
finally
{
ConnectionManager.FreeConnection(statement.ClassMap.RelationalDatabase, conn);
}
}
internal int ProcessStatementNonQuery(Statements.FreeSqlStatement statement)
{
IDbConnection connection = ConnectionManager.GetConnection(statement.ClassMap.RelationalDatabase);
try
{
return 0;
}
finally
{
ConnectionManager.FreeConnection(statement.ClassMap.RelationalDatabase,connection);
}
}
internal Cursor ProcessStatement(Statements.RetrieveStatement statement, ICollection parameters)
{
// IDbConnection connection = ConnectionManager.GetConnection(statement.ClassMap.RelationalDatabase);
// try
// {
return ProcessStatement(statement,parameters/*,connection*/,false);
// }
// finally
// {
// ConnectionManager.FreeConnection(statement.ClassMap.RelationalDatabase,connection);
// }
}
internal Cursor ProcessStatement(Statements.FreeSqlStatement statement)
{
// IDbConnection connection = ConnectionManager.GetConnection(statement.ClassMap.RelationalDatabase);
// try
// {
return ProcessStatement(statement/*,connection*/,false);
// }
// finally
// {
// ConnectionManager.FreeConnection(statement.ClassMap.RelationalDatabase,connection);
// }
}
internal Cursor ProcessStatementForProxies(Statements.RetrieveStatement statement, ICollection parameters)
{
// IDbConnection connection = ConnectionManager.GetConnection(statement.ClassMap.RelationalDatabase);
// try
// {
return ProcessStatement(statement,parameters/*,connection*/,true);
// }
// finally
// {
// ConnectionManager.FreeConnection(statement.ClassMap.RelationalDatabase,connection);
// }
}
internal Cursor ProcessStatementForProxies(Statements.FreeSqlStatement statement)
{
// IDbConnection connection = ConnectionManager.GetConnection(statement.ClassMap.RelationalDatabase);
// try
// {
return ProcessStatement(statement/*,connection*/,true);
// }
// finally
// {
// ConnectionManager.FreeConnection(statement.ClassMap.RelationalDatabase,connection);
// }
}
#region Private Processing method
private Cursor ProcessStatement(Statements.RetrieveStatement statement,ICollection parameters/*,IDbConnection connection*/,bool onlyProxies)
{
IDbConnection conn = ConnectionManager.GetConnection(statement.ClassMap.RelationalDatabase);
try
{
IDataReader dataReader = null;
using(SqlStatement sqlStatement = statement.GetSqlStatement(parameters,onlyProxies))
{
dataReader = ConnectionManager.ExecuteStatementQuery(sqlStatement,conn);
}
return new Cursor(dataReader,conn,statement.ClassMap,onlyProxies);
}
finally
{
// Do not Free Connection here, a DataReader is still opened in cursor
}
}
private Cursor ProcessStatement(Statements.FreeSqlStatement statement/*,IDbConnection connection*/,bool onlyProxies)
{
IDbConnection conn = ConnectionManager.GetConnection(statement.ClassMap.RelationalDatabase);
try
{
IDataReader dataReader = null;
using(SqlStatement sqlStatement = statement.SqlStatement)
{
dataReader = ConnectionManager.ExecuteStatementQuery(sqlStatement,conn);
}
return new Cursor(dataReader,conn,statement.ClassMap,true);
}
finally
{
// Do not Free Connection here, a DataReader is still opened in cursor
}
}
private int ProcessStatementNonQuery(Statements.DeleteStatement statement,ICollection parameters,IDbConnection connection)
{
ArrayList objects = new ArrayList();
// Create RetrieveStatement to get object for deletion
Statements.RetrieveStatement retrieveStatement = new Statements.RetrieveStatement(statement);
using(Cursor result = ProcessStatement(retrieveStatement,parameters/*,connection*/,false))
{
foreach(object obj in result)
{
objects.Add(obj);
}
}
// Delete objects
foreach(PersistentObject o in objects)
{
DeleteObject(o, statement.ClassMap, connection);
}
return objects.Count;
}
#endregion
#endregion
}
}
|