|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionRecently, I have been working with database projects within Visual Studio 2005 (yes, I'm set in my ways -- I haven't got around to VS2008 yet!!). I like the ability to source control my creation scripts and Stored Procedures, and I'm happy to use the VS IDE to write my SQL code and running individual scripts against multiple development database references is a breeze. However, deploying the finished scripts to a production database is a bit of a nightmare. Essentially, if each script file represents a single database object, you have to ensure that you run them all in the correct order -- especially if you have foreign key constraints (you do have foreign key constraints, don't you?!). Ordering all these scripts manually is a royal pain. After the second or third time of being stuck in this situation, I decided enough is enough, and set about writing a small app to do the job for me. The results of this formed the mighty The DependencyListThe To assist with this (and promote possible extension to additional collections), I started out with a basic interface: public interface IDependencyCollection<T> : ICollection<T>
{
void AddDependencies(T item, params T[] dependsOnItems);
void AddDependencies(int index, params int[] dependsOnIndices);
void RemoveDependencies(T item, params T[] noLongerDependsOnItems);
void RemoveDependencies(int index, params int[] noLongerDependsOnIndices);
void ClearDependencies();
}
This determines that I will use the basic My basic means to determine dependencies is to declare a child class that provides a It is also important that an item is not yielded more than once per enumeration, so I have also added a private enum which defines the three states of an item: not yielded, yielding, and yielded. The yield process is only really concerned with 'not yielded' and 'yielded'; the 'yielding' state is primarily used in debugging to detect circular dependencies: public class DependencyList<T> : IDependencyCollection<T>, IList<T>
{
private delegate IEnumerable<T> YielderDelegate();
private enum YieldState { None = 0, Yielding, Yielded }
private class DependencyListNode
{
internal readonly T value;
internal DependencyListNode(T value) { this.value = value; }
internal YielderDelegate Yielding;
internal YieldState state = YieldState.None;
internal void ClearYielding() { Yielding = null; }
internal IEnumerable<T> Yield() { /*...*/ }
}
//...
}
The public class DependencyList<T> : IDependencyCollection<T>, IList<T>
{
//...
private class DependencyListNode
{
//..
internal IEnumerable<T> Yield()
{
if (state == YieldState.None)
{
state = YieldState.Yielding;
if (Yielding != null)
{
IEnumerable<T> e = Yielding();
foreach (T t in e) yield return t;
}
yield return value;
state = YieldState.Yielded;
}
}
//...
}
//...
}
Implementing the interfacesMy public class DependencyList<T> : IDependencyCollection<T>, IList<T>
{
//...
private readonly List<DependencyListNode> nodes =
new List<DependencyListNode>();
#region IList<T> members
public void Insert(int index, T item)
{
nodes.Insert(index, new DependencyListNode(item));
}
public int IndexOf(T item)
{
for (int i = 0; i < nodes.Count; i++)
if (Equals(nodes[i].value, item)) return i;
return -1;
}
public void RemoveAt(int index) { nodes.RemoveAt(index); }
//...
#endregion
#region ICollection<T> members
public void Add(T item) { nodes.Add(new DependencyListNode(item)); }
public void Clear() { nodes.Clear(); }
public int Count { get { return nodes.Count; } }
//...
#endregion
//...
}
(Note that I've not included the entire interface implementation here, but you should be able to get the idea.) Next up is to add the public class DependencyList<T> : IDependencyCollection<T>, IList<T>
{
//...
public void AddDependencies(int index, params int[] dependsOnIndexes)
{
if (dependsOnIndexes == null)
throw new ArgumentNullException("dependsOnIndexes");
if (index < 0 || index >= nodes.Count)
throw new ArgumentOutOfRangeException("index",
index, "Index out of range"
);
foreach (int i in dependsOnIndexes)
if (i < 0 || i >= nodes.Count)
throw new ArgumentOutOfRangeException("dependsOnIndexes",
i, "Index out of range"
);
foreach (int i in dependsOnIndexes) nodes[index].Yielding += nodes[i].Yield;
}
//...
}
(Again, I've not fleshed out all the methods here -- Deploying Database ProjectsNow, my This consists of a basic form, with input folder and output file dialog boxes. For added functionality, I've also included a multi-select list box which is populated with all the SQL files found in the input folder. The user can choose to include/exclude certain files using this list box:
When the user clicks the OK button, each selected file is added to a new class called the Generating the ScriptThe public class ScriptGenerator
{
private readonly DependencyList<string> sqlList =
new DependencyList<string>();
private readonly Dictionary<string, int> nameIndexes =
new Dictionary<string, int>();
public void AddSqlFile(string sqlFilePath)
{
string fileName =
Path.GetFileNameWithoutExtension(sqlFilePath).ToLowerInvariant();
using (StreamReader reader = new StreamReader(sqlFilePath))
{
sqlList.Add(reader.ReadToEnd());
nameIndexes.Add(fileName, sqlList.Count - 1);
}
}
public void Generate(string outputFilePath)
{
ResolveDependencies();
using (StreamWriter writer = new StreamWriter(outputFilePath))
{
foreach (string sql in sqlList)
{
writer.WriteLine(sql);
writer.WriteLine();
writer.Flush();
}
}
}
//...
}
The observant amongst you might have noticed the public class ScriptGenerator
{
//...
private struct Qualifier
{
internal static readonly Qualifier[] QUALIFIERS = new Qualifier[] {
new Qualifier('"', '"'),
new Qualifier('[', ']'),
new Qualifier(' ', ' '),
new Qualifier(' ', '('),
new Qualifier(')', ' '),
new Qualifier(')', '('),
new Qualifier('.', '.'),
new Qualifier('.', '('),
new Qualifier('.', ' '),
new Qualifier('(', ')')
};
private readonly char start;
private readonly char end;
internal Qualifier(char start, char end)
{
this.start = start;
this.end = end;
}
internal string Qualify(string str)
{
return string.Concat(start, str, end);
}
}
//...
}
Resolving DependenciesThe public class ScriptGenerator
{
//...
private void ResolveDependencies()
{
for (int s = 0; s < sqlList.Count; s++)
{
string sql = sqlList[s]
.ToLowerInvariant()
.Replace(Environment.NewLine, "\n")
.Replace('\t', ' ');
foreach (string key in nameIndexes.Keys)
foreach (Qualifier q in Qualifier.QUALIFIERS)
if (s != nameIndexes[key] && sql.Contains(q.Qualify(key)))
{
sqlList.AddDependencies(s, nameIndexes[key]);
break;
}
}
}
//...
}
The end result is that all the SQL files are ordered appropriately, and spooled to a single output script that can be executed on the target database with far less effort than all those separate scripts. Obviously, your mileage may vary, depending on how you've written your SQL code, although most ordering errors can be resolved by adding suitable qualifiers and remembering that it is just a dumb Hope this helps with future database projects... History
Initial version. Originally posted here.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||