Click here to Skip to main content
15,896,557 members
Articles / Desktop Programming / Windows Forms

WinForms controls to develop with the Pfz.Databasing framework

Rate me:
Please Sign up or sign in to vote.
4.25/5 (6 votes)
7 Oct 2009CPOL6 min read 25.5K   633   10  
Really easy to use framework capable of generating the right controls dynamically for each data type.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using Pfz.Databasing.Extensions.DatabaseNameExtensions;
using Pfz.Databasing.Filtering;

namespace Pfz.Databasing.Managers
{
	/// <summary>
	/// Creates a "database" manager that works over files and directories instead of
	/// a real database. Good only for small projects, as there is no indexing or
	/// transaction support.
	/// </summary>
	public sealed class SerializerDatabaseManager:
		IDatabaseManager
	{
		/// <summary>
		/// Creates the SerializerDatabaseManager using the given path and the BinaryFormatter.
		/// </summary>
		public SerializerDatabaseManager(string basePath):
			this(basePath, new BinaryFormatter())
		{
		}
		
		/// <summary>
		/// Creates the SerializerDatabaseManager using the given path and formatter.
		/// </summary>
		/// <param name="basePath">The directory that is considered the "root" for the databases.</param>
		/// <param name="formatter">The formatter used to read and write the records/files.</param>
		public SerializerDatabaseManager(string basePath, IFormatter formatter)
		{
			if (string.IsNullOrEmpty(basePath))
				throw new ArgumentNullException("basePath");
			
			if (formatter == null)
				throw new ArgumentNullException("formatter");
				
			if (basePath[basePath.Length-1] != '\\')
				basePath += '\\';
				
			fBasePath = Path.GetFullPath(basePath);
			fFormatter = formatter;
		}
	
		private string fBasePath;
		/// <summary>
		/// Gets the base directory path used by this manager.
		/// </summary>
		public string BasePath
		{
			get
			{
				return fBasePath;
			}
		}
		
		private IFormatter fFormatter;
		/// <summary>
		/// Gets the formatter used by this manager.
		/// </summary>
		public IFormatter Formatter
		{
			get
			{
				return fFormatter;
			}
		}
	
		/// <summary>
		/// Uses the base path + the given name as the "database".
		/// </summary>
		public IDatabaseConnection CreateConnection(string name)
		{
			if (string.IsNullOrEmpty(name))
				throw new ArgumentNullException("name");
				
			return new SerializerDatabaseConnection(fFormatter, this.BasePath + name + '\\');
		}

		/// <summary>
		/// Tries to load a record of type T with the given primary key values.
		/// </summary>
		public T TryLoadByPrimaryKey<T>(IDatabaseConnection connection, params object[] primaryKeyValues)
		where
			T: class, IRecord
		{
			RecordImplementer.GetImplementedRecordsAssemblyFor<RecordClassGenerator>(typeof(T).Assembly);
			var realConnection = (SerializerDatabaseConnection)connection;
			string name = i_GetFileName(typeof(T), realConnection.fPath, primaryKeyValues);
			
			IRecord record;
			if (p_GetTransactionRecord(connection, name, out record))
				return (T)record;
				
			FileStream stream = null;
			try
			{
				stream = File.OpenRead(name.ToString());
			}
			catch
			{
			}
			
			if (stream == null)
				return null;
			
			using(stream)
			{
				object obj = fFormatter.Deserialize(stream);
				RecordBase recordBase = (RecordBase)obj;
				recordBase.SetRecordMode(RecordMode.ReadOnly);
				return (T)obj;
			}
		}
		
		private static bool p_GetTransactionRecord(IDatabaseConnection connection, string name, out IRecord record)
		{
			if (!connection.HasActiveTransaction)
			{
				record = null;
				return false;
			}
			
			if (SerializerDatabaseConnection.fChangedRecords.TryGetValue(name, out record))
			{
				if (record.GetRecordMode() == RecordMode.ReadOnly)
				{
					record = null;
					return true;
				}
					
				RecordBase rb = (RecordBase)record.Clone();
				rb.SetUntypedOldRecord(null);
				rb.SetRecordMode(RecordMode.ReadOnly);
				record = rb;
				return true;
			}
			
			record = null;
			return false;
		}

		internal static string i_GetFileName(Type recordType, string path, object[] primaryKeyValues)
		{
			StringBuilder name = new StringBuilder(path);

			name.Append(recordType.GetDatabaseName());

			foreach (var value in primaryKeyValues)
			{
				name.Append('\\');
				name.Append(value);
			}
			
			return name.ToString();
		}

		/// <summary>
		/// Creates a new record.
		/// </summary>
		public T Create<T>(IDatabaseConnection connection)
		where
			T: class, IRecord
		{
			return RecordImplementer.Create<RecordClassGenerator, T>();
		}

		/// <summary>
		/// Applies the given record.
		/// </summary>
		public T Apply<T>(IDatabaseConnection connection, T record)
		where
			T: class, IRecord
		{
			var realConnection = (SerializerDatabaseConnection)connection;
			object[] primaryKeyValues = record.GetPrimaryKeyValues();
			string name = i_GetFileName(record.GetType(), realConnection.fPath, primaryKeyValues);
			
			IRecord recordAlreadyInTransaction;
			SerializerDatabaseConnection.fChangedRecords.TryGetValue(name, out recordAlreadyInTransaction);

			var recordMode = record.GetRecordMode();
			switch(recordMode)
			{
				case RecordMode.Insert:
					if (recordAlreadyInTransaction != null)
						if (recordAlreadyInTransaction.GetRecordMode() != RecordMode.ReadOnly)
							throw new DatabaseException("There is already a record with the same primary key.");
					
					goto _update;
				
				case RecordMode.Update:
					_update:

					SerializerDatabaseConnection.fChangedRecords[name] = (IRecord)record.Clone();
					
					return record;
				
				case RecordMode.ReadOnly:
					if (!record.GetMustDeleteOnApply())
						throw new DatabaseException("The actual record is read-only and must not be applied.");
					
					SerializerDatabaseConnection.fChangedRecords[name] = record;
					
					return null;
				
				default:
					throw new DatabaseException("Unknown RecordMode " + recordMode + ".");
			}
		}

		/// <summary>
		/// Always returns true.
		/// </summary>
		public bool MustCloneBeforeApply
		{
			get 
			{
				return true;
			}
		}


		/// <summary>
		/// Loads records using the given filter.
		/// </summary>
		public IFastEnumerator<T> FastLoadByFilterGroup<T>(IDatabaseConnection connection, FilterGroup filterGroup)
		where
			T: class, IRecord
		{
			RecordImplementer.GetImplementedRecordsAssemblyFor<RecordClassGenerator>(typeof(T).Assembly);

			var realConnection = (SerializerDatabaseConnection)connection;
			List<FileInfo> files = new List<FileInfo>();
			var directoryInfo = new DirectoryInfo(realConnection.fPath + typeof(T).GetDatabaseName());
			if (directoryInfo.Exists)
				p_GetFiles(files, directoryInfo);
			
			return new p_FastLoadByFilterGroup<T>(realConnection, files, filterGroup, fFormatter);
		}
		
		/// <summary>
		/// Return the count of records that matches the given filter.
		/// </summary>
		public int CountRecordsByFilterGroup<T>(IDatabaseConnection connection, FilterGroup filterGroup)
		where
			T: class, IRecord
		{
			RecordImplementer.GetImplementedRecordsAssemblyFor<RecordClassGenerator>(typeof(T).Assembly);

			int count = 0;
			
			using(var enumerator = FastLoadByFilterGroup<T>(connection, filterGroup))
				while(enumerator.GetNext() != null)
					count++;
			
			return count;
		}

		private void p_GetFiles(List<FileInfo> files, DirectoryInfo directoryInfo)
		{
			foreach(var subDirectory in directoryInfo.GetDirectories())
				p_GetFiles(files, subDirectory);
			
			var directoryFiles = directoryInfo.GetFiles();
			files.AddRange(directoryFiles);
		}


		/// <summary>
		/// Loads records using the AdvancedLoadParameters.
		/// </summary>
		public IFastEnumerator<object[]> AdvancedLoad(IDatabaseConnection connection, AdvancedLoadParameters parameters)
		{
			var realConnection = (SerializerDatabaseConnection)connection;
			List<FileInfo> files = new List<FileInfo>();
			var directoryInfo = new DirectoryInfo(realConnection.fPath + parameters.InitialRecordType.FullName);
			if (directoryInfo.Exists)
				p_GetFiles(files, directoryInfo);
				
			return new p_AdvancedLoad(realConnection, files, parameters, fFormatter);
		}
		
		T IDatabaseManager.CreateUserImplementation<T>(T record)
		{
			throw new NotImplementedException();
		}

		IFastEnumerator<T> IDatabaseManager.FastLoadByPartialSql<T>(IDatabaseConnection connection, string sql, params object[] parameterValues)
		{
			throw new NotImplementedException("SerializerDatabaseManager does not support loading by SQL strings.");
		}
		
		private static bool p_Matches(FilterGroup group, object record)
		{
			if (group == null)
				return true;
		
			if (group.fFilters.Count == 0 && group.fSubGroups.Count == 0)
				return true;
			
			if (group.GroupMode == FilterGroupMode.And)
			{
				foreach(var filter in group.fFilters)
					if (!p_Matches(filter, record))
						return false;
						
				foreach(var subGroup in group.fSubGroups)
					if (!p_Matches(subGroup, record))
						return false;
				
				return true;
			}
			
			foreach(var filter in group.fFilters)
				if (p_Matches(filter, record))
					return true;
					
			foreach(var subGroup in group.fSubGroups)
				if (p_Matches(subGroup, record))
					return true;
			
			return false;
		}
		private static bool p_Matches(Filter filter, object record)
		{
			object value = filter.Path.GetValue(record, filter.fTypePath);
			
			switch(filter.Operation)
			{
				case FilterOperation.DifferentThan:
					return !object.Equals(value, filter.Value);
					
				case FilterOperation.EqualTo:
					return object.Equals(value, filter.Value);
					
				case FilterOperation.GreaterOrEqual:
					return p_GetComparableValue(value, filter.Value) >= 0;
				
				case FilterOperation.GreaterThan:
					return p_GetComparableValue(value, filter.Value) > 0;

				case FilterOperation.LessOrEqual:
					return p_GetComparableValue(value, filter.Value) <= 0;
					
				case FilterOperation.LessThan:
					return p_GetComparableValue(value, filter.Value) < 0;

				case FilterOperation.In:
					throw new DatabaseException("SerializerDatabaseManager does not support IN operator.");
					
				case FilterOperation.NotIn:
					throw new DatabaseException("SerializerDatabaseManager does not support NOTIN operator.");
					
				case FilterOperation.NotLike:
					throw new DatabaseException("SerializerDatabaseManager does not support NOTLIKE operator.");
				
				case FilterOperation.Like:
					throw new DatabaseException("SerializerDatabaseManager does not support LIKE operator.");
					
				case FilterOperation.StringStartsWith:
					if (value == null)
						return false;
						
					return value.ToString().StartsWith(filter.Value.ToString());

				case FilterOperation.StringNotStartsWith:
					if (value == null)
						return false;
						
					return !value.ToString().StartsWith(filter.Value.ToString());
					
				case FilterOperation.StringEndsWith:
					if (value == null)
						return false;
						
					return value.ToString().EndsWith(filter.Value.ToString());

				case FilterOperation.StringNotEndsWith:
					if (value == null)
						return false;
						
					return !value.ToString().EndsWith(filter.Value.ToString());

				case FilterOperation.StringContains:
					if (value == null)
						return false;
						
					return value.ToString().Contains(filter.Value.ToString());

				case FilterOperation.StringNotContains:
					if (value == null)
						return false;
						
					return !value.ToString().Contains(filter.Value.ToString());

				default:
					throw new DatabaseException("Unknown filterOperation " + filter.Operation + ".");
			}
		}

		private static int p_GetComparableValue(object filterValue, object value)
		{
			IComparable comparable = (IComparable)filterValue;
			return comparable.CompareTo(value);
		}

		private sealed class p_FastLoadByFilterGroup<T>:
			IFastEnumerator<T>
		where
			T: class, IRecord
		{
			private SerializerDatabaseConnection fConnection;
			private List<FileInfo> fFiles;
			private FilterGroup fGroup;
			private int fFileIndex;
			private IFormatter fFormatter;
			
			public p_FastLoadByFilterGroup(SerializerDatabaseConnection connection, List<FileInfo> files, FilterGroup group, IFormatter formatter)
			{
				fConnection = connection;
				fFiles = files;
				fGroup = group;
				fFormatter = formatter;
			}
			public void Dispose()
			{
			}

			public T GetNext()
			{
				while(true)
				{
					if (fFileIndex >= fFiles.Count)
						return null;

					var fileInfo = fFiles[fFileIndex];
					fFileIndex++;
					
					T result;
					
					IRecord record;
					if (p_GetTransactionRecord(fConnection, fileInfo.FullName, out record))
					{
						if (record == null)
							continue;
							
						result = (T)record;
					}
					else
					{
						object obj;
						using(var stream = fileInfo.OpenRead())
							obj = fFormatter.Deserialize(stream);
						
						result = (T)obj;
						
						RecordBase recordBase = (RecordBase)obj;
						recordBase.SetRecordMode(RecordMode.ReadOnly);
					}
					
					if (fGroup == null)
						return result;
					
					if (p_Matches(fGroup, result))
						return result;
				}
			}

			object IFastEnumerator.GetNext()
			{
				return GetNext();
			}
		}
		private sealed class p_AdvancedLoad:
			IFastEnumerator<object[]>,
			IEqualityComparer<object[]>
		{
			private SerializerDatabaseConnection fConnection;
			private List<FileInfo> fFiles;
			private AdvancedLoadParameters fParameters;
			private int fFileIndex;
			private IFormatter fFormatter;
			private object[] fResult;
			private HashSet<object[]> fHashSet;
			
			public p_AdvancedLoad(SerializerDatabaseConnection connection, List<FileInfo> files, AdvancedLoadParameters parameters, IFormatter formatter)
			{
				fConnection = connection;
				fFiles = files;
				fParameters = parameters;
				fFormatter = formatter;
				fResult = new object[parameters.SelectPaths.Count];
				
				if (parameters.Distinct)
					fHashSet = new HashSet<object[]>(this);
			}
			public void Dispose()
			{
			}

			public object[] GetNext()
			{
				while(true)
				{
					if (fFileIndex >= fFiles.Count)
						return null;

					var fileInfo = fFiles[fFileIndex];
					fFileIndex++;
					
					object result;

					IRecord record;
					if (p_GetTransactionRecord(fConnection, fileInfo.Name, out record))
					{
						if (record == null)
							continue;
							
						result = record;
					}
					else
					{
						using(var stream = fileInfo.OpenRead())
							result = fFormatter.Deserialize(stream);
					}
						
					if (p_Matches(fParameters.Filter, result))
					{
						var resultValues = p_GetValues(result);
						
						if (fHashSet == null || fHashSet.Add(resultValues))
							return resultValues;
					}
				}
			}

			private object[] p_GetValues(object result)
			{
				int itemIndex = -1;
				foreach(var path in fParameters.SelectPaths)
				{
					itemIndex++;

					Type[] typePaths = null;
					if (fParameters.TypePaths != null)
						typePaths = fParameters.TypePaths[itemIndex];
				
					object value = path.GetValue(result, typePaths);
					fResult[itemIndex] = value;
				}
				
				return fResult;
			}

			object IFastEnumerator.GetNext()
			{
				return GetNext();
			}

			public bool Equals(object[] x, object[] y)
			{
				return x.SequenceEqual(y);
			}
			public int GetHashCode(object[] values)
			{
				int result = 0;
				
				foreach(var value in values)
					if (value != null)
						result ^= value.GetHashCode();
					
				return result;
			}
		}

		/// <summary>
		/// Gets the implemented class type for the given record type.
		/// </summary>
		public Type GetImplementedTypeFor(Type type)
		{
			return RecordImplementer.GetImplementedTypeFor<RecordClassGenerator>(type);
		}

		/// <summary>
		/// Gets the type of the generator used by this class.
		/// Returns the typeof(RecordClassGenerator).
		/// </summary>
		public Type RecordClassGeneratorType
		{
			get
			{
				return typeof(RecordClassGenerator);
			}
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions