diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..bdb0cab --- /dev/null +++ b/.gitattributes @@ -0,0 +1,17 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/Effort.Extra.Tests/Behaviours/CreatesNewTableBehaviour.cs b/Effort.Extra.Tests/Behaviours/CreatesNewTableBehaviour.cs new file mode 100644 index 0000000..e270d9d --- /dev/null +++ b/Effort.Extra.Tests/Behaviours/CreatesNewTableBehaviour.cs @@ -0,0 +1,21 @@ + +namespace Effort.Extra.Tests.Behaviours +{ + using System; + using System.Collections.Generic; + using Machine.Specifications; + + [Behaviors] + internal class CreatesNewTableBehaviour + { + protected static Exception thrown_exception; + protected static string table_name; + protected static IList result; + + It does_not_throw_an_exception = () => thrown_exception.ShouldBeNull(); + + It the_table_is_not_null = () => result.ShouldNotBeNull(); + + It the_table_is_empty = () => result.ShouldBeEmpty(); + } +} diff --git a/Effort.Extra.Tests/Behaviours/ReturnsExistingTableBehaviour.cs b/Effort.Extra.Tests/Behaviours/ReturnsExistingTableBehaviour.cs new file mode 100644 index 0000000..c58102f --- /dev/null +++ b/Effort.Extra.Tests/Behaviours/ReturnsExistingTableBehaviour.cs @@ -0,0 +1,27 @@ + +namespace Effort.Extra.Tests.Behaviours +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Machine.Specifications; + + [Behaviors] + internal class ReturnsExistingTableBehaviour + { + protected static Exception thrown_exception; + protected static string table_name; + protected static IList result; + protected static IList expected_result; + + It does_not_throw_an_exception = () => thrown_exception.ShouldBeNull(); + + It the_table_is_not_null = () => result.ShouldNotBeNull(); + + It the_table_contains_the_existing_items = () => + { + expected_result.All(result.Contains).ShouldBeTrue(); + result.All(expected_result.Contains).ShouldBeTrue(); + }; + } +} \ No newline at end of file diff --git a/Effort.Extra.Tests/Builder.cs b/Effort.Extra.Tests/Builder.cs new file mode 100644 index 0000000..b5afa7b --- /dev/null +++ b/Effort.Extra.Tests/Builder.cs @@ -0,0 +1,25 @@ + +namespace Effort.Extra.Tests +{ + using System; + using System.Linq; + using System.Reflection; + using Effort.DataLoaders; + + internal static class Builder + { + public static TableDescription CreateTableDescription(string name, Type type) + { + var ctor = typeof(TableDescription).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).First(); + var columns = type.GetProperties().Select(CreateColumnDescription).ToArray(); + + return (TableDescription)ctor.Invoke(new object[] { name, columns }); + } + + public static ColumnDescription CreateColumnDescription(PropertyInfo property) + { + var ctor = typeof(ColumnDescription).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).First(); + return (ColumnDescription)ctor.Invoke(new object[] { property.Name, property.PropertyType }); + } + } +} \ No newline at end of file diff --git a/Effort.Extra.Tests/Effort.Extra.Tests.csproj b/Effort.Extra.Tests/Effort.Extra.Tests.csproj new file mode 100644 index 0000000..437fa5f --- /dev/null +++ b/Effort.Extra.Tests/Effort.Extra.Tests.csproj @@ -0,0 +1,185 @@ + + + + + Debug + AnyCPU + {064965F8-9D9C-4281-A4DB-2C14C33F52D4} + Library + Properties + Effort.Extra.Tests + Effort.Extra.Tests + v4.5 + 512 + 1 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + True + False + True + False + False + True + True + True + True + True + True + True + True + True + False + True + False + True + False + False + False + False + True + False + True + True + True + False + False + + + + + + + + True + False + False + True + Full + Build + 0 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + True + False + True + False + False + True + True + True + True + True + True + True + True + True + False + True + False + True + False + False + False + False + True + False + True + True + True + False + True + + + + + + + + True + False + False + True + Full + Build + 0 + + + + False + ..\packages\Effort.1.1.4\lib\net45\Effort.dll + + + ..\packages\Machine.Fakes.2.5.0\lib\net40\Machine.Fakes.dll + + + ..\packages\Machine.Fakes.NSubstitute.2.5.0\lib\net40\Machine.Fakes.Adapters.NSubstitute.dll + + + ..\packages\Machine.Specifications.0.9.0\lib\net45\Machine.Specifications.dll + + + ..\packages\Machine.Specifications.0.9.0\lib\net45\Machine.Specifications.Clr4.dll + + + ..\packages\Machine.Specifications.Should.0.7.2\lib\net45\Machine.Specifications.Should.dll + + + False + ..\packages\NMemory.1.0.1\lib\net45\NMemory.dll + + + ..\packages\NSubstitute.1.7.2.0\lib\NET45\NSubstitute.dll + + + + + + + + + + + + + + + + + + + + + + + + + + {0545d98f-bb8c-4689-8214-72cb8135a0ea} + Effort.Extra + + + + + + + + \ No newline at end of file diff --git a/Effort.Extra.Tests/Fella.cs b/Effort.Extra.Tests/Fella.cs new file mode 100644 index 0000000..7a17779 --- /dev/null +++ b/Effort.Extra.Tests/Fella.cs @@ -0,0 +1,8 @@ + +namespace Effort.Extra.Tests +{ + public class Fella + { + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/Effort.Extra.Tests/ObjectData.cs b/Effort.Extra.Tests/ObjectData.cs new file mode 100644 index 0000000..946a18a --- /dev/null +++ b/Effort.Extra.Tests/ObjectData.cs @@ -0,0 +1,264 @@ + +namespace Effort.Extra.Tests +{ + using System; + using System.Collections.Generic; + using Effort.Extra.Tests.Behaviours; + using Machine.Fakes; + using Machine.Specifications; + + internal class ObjectData + { + public class Ctor + { + [Subject("ObjectData.Ctor")] + public class when_class_is_instantiated : WithSubject + { + It generates_a_new_identifier = () => Subject.Identifier.ShouldNotEqual(Guid.Empty); + } + } + + public class Table + { + [Subject("ObjectData.Table")] + public abstract class table_context : WithSubject + { + protected static Exception thrown_exception; + protected static string table_name; + protected static IList result; + + Because of = () => thrown_exception = Catch.Exception( + () => result = Subject.Table(table_name)); + } + + public class when_table_with_name_does_not_exist : table_context + { + Establish context = () => + { + table_name = "Knights_of_the_Round_Table"; + }; + + Behaves_like a_new_table_is_created; + } + + public class when_table_with_name_exists : table_context + { + protected static IList expected_result; + + Establish context = () => + { + table_name = "Knights_of_the_Round_Table"; + expected_result = Subject.Table(table_name); + expected_result.Add("Arthur, King of the Britons"); + expected_result.Add("Sir Bedevere, the Wise"); + expected_result.Add("Sir Lancelot, the Brave"); + expected_result.Add("Sir Galahad, the Pure"); + expected_result.Add("Sir Robin, the not quite as brave as Sir Lancelot"); + }; + + Behaves_like the_existing_table_is_returned; + } + + public class when_table_with_name_exists_but_is_of_different_type : table_context + { + Establish context = () => + { + table_name = "Knights_of_the_Round_Table"; + var table = Subject.Table(table_name); + table.Add("Arthur, King of the Britons"); + table.Add("Sir Bedevere, the Wise"); + table.Add("Sir Lancelot, the Brave"); + table.Add("Sir Galahad, the Pure"); + table.Add("Sir Robin, the not quite as brave as Sir Lancelot"); + }; + + It throws_an_invalid_operation_exception = + () => thrown_exception.ShouldBeOfExactType(); + } + } + + public class HasTable + { + [Subject("ObjectData.HasTable")] + public abstract class has_table_context : WithSubject + { + protected static Exception thrown_exception; + protected static string table_name; + protected static bool result; + + private Because of = () => thrown_exception = Catch.Exception( + () => result = Subject.HasTable(table_name)); + } + + public class when_table_name_is_null : has_table_context + { + Establish context = () => table_name = null; + + It throws_an_argument_null_exception = + () => thrown_exception.ShouldBeOfExactType(); + } + + public class when_table_name_is_empty_string : has_table_context + { + Establish context = () => table_name = String.Empty; + + It throws_an_argument_exception = + () => thrown_exception.ShouldBeOfExactType(); + } + + public class when_table_name_is_whitespace : has_table_context + { + Establish context = () => table_name = " "; + + It throws_an_argument_exception = + () => thrown_exception.ShouldBeOfExactType(); + } + + public class when_table_does_not_exist : has_table_context + { + Establish context = () => + { + table_name = "Knights_of_the_Round_Table"; + }; + + It does_not_throw_an_exception = () => thrown_exception.ShouldBeNull(); + + It the_result_is_false = () => result.ShouldBeFalse(); + } + + public class when_table_exists : has_table_context + { + Establish context = () => + { + table_name = "Knights_of_the_Round_Table"; + Subject.Table(table_name); + }; + + It does_not_throw_an_exception = () => thrown_exception.ShouldBeNull(); + + It the_result_is_true = () => result.ShouldBeTrue(); + } + } + + public class TableType + { + [Subject("ObjectData.TableType")] + public abstract class table_type_context : WithSubject + { + protected static Exception thrown_exception; + protected static string table_name; + protected static Type result; + + Because of = () => thrown_exception = Catch.Exception( + () => result = Subject.TableType(table_name)); + } + + public class when_table_name_is_null : table_type_context + { + Establish context = () => table_name = null; + + It throws_an_argument_null_exception = + () => thrown_exception.ShouldBeOfExactType(); + } + + public class when_table_name_is_empty_string : table_type_context + { + Establish context = () => table_name = String.Empty; + + It throws_an_argument_exception = + () => thrown_exception.ShouldBeOfExactType(); + } + + public class when_table_name_is_whitespace : table_type_context + { + Establish context = () => table_name = " "; + + It throws_an_argument_exception = + () => thrown_exception.ShouldBeOfExactType(); + } + + public class when_table_does_not_exist : table_type_context + { + Establish context = () => table_name = "Sir_not_appearing_in_this_test."; + + It throws_an_invalid_operation_exception = + () => thrown_exception.ShouldBeOfExactType(); + } + + public class when_table_exists : table_type_context + { + Establish context = () => + { + table_name = "Knights_of_the_Round_Table"; + Subject.Table(table_name); + }; + + It does_not_throw_an_exception = () => thrown_exception.ShouldBeNull(); + + It the_result_is_correct = () => result.ShouldEqual(typeof(string)); + } + } + + public class GetTable + { + [Subject("ObjectData.GetTable")] + public abstract class get_table_context : WithSubject + { + protected static Exception thrown_exception; + protected static string table_name; + protected static object result; + + Because of = () => thrown_exception = Catch.Exception( + () => result = Subject.GetTable(table_name)); + } + + public class when_table_name_is_null : get_table_context + { + Establish context = () => table_name = null; + + It throws_an_argument_null_exception = + () => thrown_exception.ShouldBeOfExactType(); + } + + public class when_table_name_is_empty_string : get_table_context + { + Establish context = () => table_name = String.Empty; + + It throws_an_argument_exception = + () => thrown_exception.ShouldBeOfExactType(); + } + + public class when_table_name_is_whitespace : get_table_context + { + Establish context = () => table_name = " "; + + It throws_an_argument_exception = + () => thrown_exception.ShouldBeOfExactType(); + } + + public class when_table_does_not_exist : get_table_context + { + Establish context = () => table_name = "Sir_not_appearing_in_this_test."; + + It does_not_throw_an_exception = () => thrown_exception.ShouldBeNull(); + + It the_result_is_null = () => result.ShouldBeNull(); + } + + public class when_table_exists : get_table_context + { + static object expected_result; + + Establish context = () => + { + table_name = "Knights_of_the_Round_Table"; + expected_result = Subject.Table(table_name); + }; + + It does_not_throw_an_exception = () => thrown_exception.ShouldBeNull(); + + It the_result_is_correct = () => result.ShouldEqual(expected_result); + } + } + } +} diff --git a/Effort.Extra.Tests/ObjectDataCollection.cs b/Effort.Extra.Tests/ObjectDataCollection.cs new file mode 100644 index 0000000..a0134d8 --- /dev/null +++ b/Effort.Extra.Tests/ObjectDataCollection.cs @@ -0,0 +1,156 @@ + +namespace Effort.Extra.Tests +{ + using System; + using Machine.Fakes; + using Machine.Specifications; + + internal class ObjectDataCollection + { + public class GetKeyForItem + { + [Subject("ObjectDataCollection.GetKeyForItem")] + public abstract class get_key_for_item : WithSubject + { + protected static Exception thrown_exception; + protected static Extra.ObjectData item; + protected static Guid result; + + Because of = () => thrown_exception = Catch.Exception( + () => result = Subject.GetKeyForItem(item)); + } + + public class when_item_is_null : get_key_for_item + { + Establish context = () => + { + item = null; + }; + + It throws_an_argument_null_exception = + () => thrown_exception.ShouldBeOfExactType(); + } + + public class when_item_is_valid : get_key_for_item + { + Establish context = () => + { + item = new Extra.ObjectData(); + }; + + It does_not_throw_an_exception = () => thrown_exception.ShouldBeNull(); + + It the_result_is_the_correct_value = () => result.ShouldEqual(item.Identifier); + } + } + + public class AddOrUpdate + { + [Subject("ObjectDataCollection.AddOrUpdate")] + public abstract class add_or_update_context : WithSubject + { + protected static Exception thrown_exception; + protected static Extra.ObjectData data; + + Because of = () => thrown_exception = Catch.Exception(() => Subject.AddOrUpdate(data)); + } + + public class when_data_is_not_already_in_the_collection : add_or_update_context + { + Establish context = () => + { + data = new Extra.ObjectData(); + }; + + It does_not_throw_an_exception = () => thrown_exception.ShouldBeNull(); + + It the_data_is_added_to_the_collection = () => Subject.Contains(data); + } + + public class when_data_is_already_in_the_collection : add_or_update_context + { + static Extra.ObjectData existing; + + Establish context = () => + { + var guid = Guid.NewGuid(); + existing = new StubObjectData(guid); + Subject.Add(existing); + data = new StubObjectData(guid); + }; + + It does_not_throw_an_exception = () => thrown_exception.ShouldBeNull(); + + It the_data_is_added_to_the_collection = () => Subject.Contains(data); + + It the_existing_item_is_not_longer_in_the_collection = + () => Subject[existing.Identifier].ShouldNotBeTheSameAs(existing); + } + + public class StubObjectData : Extra.ObjectData + { + private readonly Guid identifier; + + public StubObjectData(Guid identifier) + { + this.identifier = identifier; + } + + internal override Guid Identifier { get { return identifier; } } + } + } + + public class TryGetValue + { + [Subject("ObjectDataCollection.TryGetValue")] + public abstract class try_get_value_context : WithSubject + { + protected static Exception thrown_exception; + protected static Guid key; + protected static Extra.ObjectData data; + protected static bool result; + + Because of = () => thrown_exception = Catch.Exception( + () => result = Subject.TryGetValue(key, out data)); + } + + public class when_key_does_not_exist : try_get_value_context + { + Establish context = () => + { + key = Guid.NewGuid(); + }; + + It does_not_throw_an_exception = () => thrown_exception.ShouldBeNull(); + + It the_data_should_be_null = () => data.ShouldBeNull(); + + It the_result_is_false = () => result.ShouldBeFalse(); + } + + public class when_key_exists : try_get_value_context + { + Establish context = () => + { + var item = new Extra.ObjectData(); + Subject.Add(item); + key = item.Identifier; + }; + + It does_not_throw_an_exception = () => thrown_exception.ShouldBeNull(); + + It the_data_should_not_be_null = () => data.ShouldNotBeNull(); + + It the_result_is_true = () => result.ShouldBeTrue(); + } + } + + public class StubObjectDataCollection : Extra.ObjectDataCollection + { + public new Guid GetKeyForItem(Extra.ObjectData item) + { + return base.GetKeyForItem(item); + } + } + } +} diff --git a/Effort.Extra.Tests/ObjectDataLoader.cs b/Effort.Extra.Tests/ObjectDataLoader.cs new file mode 100644 index 0000000..2999761 --- /dev/null +++ b/Effort.Extra.Tests/ObjectDataLoader.cs @@ -0,0 +1,104 @@ + +namespace Effort.Extra.Tests +{ + using System; + using System.Collections.Generic; + using Effort.DataLoaders; + using Machine.Fakes; + using Machine.Specifications; + + internal class ObjectDataLoader + { + public class Ctor + { + [Subject("ObjectDataLoader.Ctor")] + public abstract class ctor_context : WithFakes + { + protected static Exception thrown_exception; + protected static Extra.ObjectData data; + protected static Extra.ObjectDataLoader subject; + + Because of = () => thrown_exception = Catch.Exception( + () => subject = new Extra.ObjectDataLoader(data)); + } + + public class when_data_is_null : ctor_context + { + Establish context = () => + { + data = null; + }; + + It throws_an_argument_null_exception = + () => thrown_exception.ShouldBeOfExactType(); + } + + public class when_data_is_valid : ctor_context + { + Establish context = () => + { + data = new Extra.ObjectData(); + }; + + It does_not_throw_an_exception = () => thrown_exception.ShouldBeNull(); + + It the_data_identifier_is_assigned_to_the_argument = () => + subject.Argument.ShouldEqual(data.Identifier.ToString()); + } + } + + public class CreateTableDataLoaderFactory + { + [Subject("ObjectDataLoader.CreateTableDataLoaderFactory")] + public abstract class create_table_data_loader_factory_context : WithSubject + { + protected static Exception thrown_exception; + protected static Extra.ObjectData data; + protected static ITableDataLoaderFactory result; + + Establish context = () => + { + data = new Extra.ObjectData(); + Configure(x => x.For().Use(() => data)); + }; + + Because of = () => thrown_exception = Catch.Exception( + () => result = Subject.CreateTableDataLoaderFactory()); + } + + public class when_argument_is_not_valid_guid : create_table_data_loader_factory_context + { + Establish context = () => + { + Subject.Argument = "Oh noes!"; + }; + + It throws_an_invalid_operation_exception = + () => thrown_exception.ShouldBeOfExactType(); + } + + public class when_argument_does_not_exist_in_collection : create_table_data_loader_factory_context + { + Establish context = () => + { + Subject.Argument = Guid.NewGuid().ToString(); + }; + + It throws_a_key_not_found_exception = () => + thrown_exception.ShouldBeOfExactType(); + } + + public class when_argument_is_valid_and_exists_in_collection : create_table_data_loader_factory_context + { + Establish context = () => + { + Subject.Argument = data.Identifier.ToString(); + }; + + It does_not_throw_an_exception = () => thrown_exception.ShouldBeNull(); + + It the_result_is_not_null = () => result.ShouldNotBeNull(); + } + } + } +} diff --git a/Effort.Extra.Tests/ObjectDataLoaderFactory.cs b/Effort.Extra.Tests/ObjectDataLoaderFactory.cs new file mode 100644 index 0000000..757e130 --- /dev/null +++ b/Effort.Extra.Tests/ObjectDataLoaderFactory.cs @@ -0,0 +1,105 @@ + +namespace Effort.Extra.Tests +{ + using System; + using Effort.DataLoaders; + using Machine.Fakes; + using Machine.Specifications; + + internal class ObjectDataLoaderFactory + { + public class Ctor + { + [Subject("ObjectDataLoaderFactory.Ctor")] + public abstract class ctor_context : WithFakes + { + protected static Exception thrown_exception; + protected static Extra.ObjectData data; + protected static Extra.ObjectDataLoaderFactory subject; + + Because of = () => thrown_exception = Catch.Exception( + () => subject = new Extra.ObjectDataLoaderFactory(data)); + } + + public class when_data_is_null : ctor_context + { + Establish context = () => + { + data = null; + }; + + It throws_an_argument_null_exception = + () => thrown_exception.ShouldBeOfExactType(); + } + + public class when_data_is_valid : ctor_context + { + Establish context = () => + { + data = new Extra.ObjectData(); + }; + + It does_not_throw_an_exception = () => thrown_exception.ShouldBeNull(); + } + } + + public class CreateTableDataLoader + { + [Subject("ObjectDataLoaderFactory.CreateTableDataLoader")] + public abstract class create_table_data_loader : WithSubject + { + protected static Extra.ObjectData data; + protected static Exception thrown_exception; + protected static TableDescription table; + protected static ITableDataLoader result; + + Establish context = () => + { + data = new Extra.ObjectData(); + Configure(x => x.For().Use(() => data)); + }; + + Because of = () => thrown_exception = Catch.Exception( + () => result = Subject.CreateTableDataLoader(table)); + } + + public class when_table_is_null : create_table_data_loader + { + Establish context = () => + { + table = null; + }; + + It throws_an_argument_null_exception = () => + thrown_exception.ShouldBeOfExactType(); + } + + public class when_there_is_no_data_for_table : create_table_data_loader + { + Establish context = () => + { + table = Builder.CreateTableDescription("Fred", typeof(object)); + }; + + It does_not_throw_an_exception = () => thrown_exception.ShouldBeNull(); + + It the_result_is_empty_table_data_loader = + () => result.ShouldBeOfExactType(); + } + + public class when_there_is_data_for_table : create_table_data_loader + { + Establish context = () => + { + data.Table().Add(new Fella { Name = "Fred" }); + table = Builder.CreateTableDescription("Fella", typeof(Fella)); + }; + + It does_not_throw_an_exception = () => thrown_exception.ShouldBeNull(); + + It the_result_is_entity_table_data_loader = + () => result.ShouldBeOfExactType>(); + } + } + } +} diff --git a/Effort.Extra.Tests/ObjectTableDataLoader.cs b/Effort.Extra.Tests/ObjectTableDataLoader.cs new file mode 100644 index 0000000..87ef082 --- /dev/null +++ b/Effort.Extra.Tests/ObjectTableDataLoader.cs @@ -0,0 +1,101 @@ + +namespace Effort.Extra.Tests +{ + using System; + using System.Collections.Generic; + using Effort.DataLoaders; + using Machine.Fakes; + using Machine.Specifications; + + internal class ObjectTableDataLoader + { + public class Ctor + { + [Subject("ObjectTableDataLoader.Ctor")] + public abstract class ctor_context : WithFakes + { + protected static Exception thrown_exception; + protected static TableDescription table; + protected static IEnumerable entities; + protected static ObjectTableDataLoader subject; + + Because of = () => thrown_exception = Catch.Exception( + () => subject = new ObjectTableDataLoader(table, entities)); + } + + public class when_table_is_null : ctor_context + { + Establish context = () => + { + table = null; + entities = new List(); + }; + + It throws_an_argument_null_exception = + () => thrown_exception.ShouldBeOfExactType(); + } + + public class when_entities_is_null : ctor_context + { + Establish context = () => + { + table = Builder.CreateTableDescription("Fella", typeof(Fella)); + entities = null; + }; + + It throws_an_argument_null_exception = + () => thrown_exception.ShouldBeOfExactType(); + } + + public class when_arguments_are_valid : ctor_context + { + Establish context = () => + { + table = Builder.CreateTableDescription("Fella", typeof(Fella)); + entities = new List(); + }; + + It does_not_throw_an_exception = () => thrown_exception.ShouldBeNull(); + } + } + + public class CreateFormatter + { + [Subject("ObjectTableDataLoader.CreateFormatter")] + public abstract class create_formatter_context : WithSubject + { + protected static Exception thrown_exception; + protected static Func formatter; + + Because of = () => thrown_exception = Catch.Exception( + () => formatter = Subject.CreateFormatter()); + } + + public class when_formatter_is_created : create_formatter_context + { + It does_not_throw_an_exception = () => thrown_exception.ShouldBeNull(); + + It the_formatter_is_not_null = () => formatter.ShouldNotBeNull(); + + It the_formatter_behaves_correctly = () => + { + var formatted = formatter(new Fella { Name = "Fred" }); + formatted.Length.ShouldEqual(1); + formatted[0].ShouldEqual("Fred"); + }; + } + } + + public class StubObjectTableDataLoader : ObjectTableDataLoader + { + public StubObjectTableDataLoader() + : base(Builder.CreateTableDescription("Fella", typeof(Fella)), new List()) + { } + + public new Func CreateFormatter() + { + return base.CreateFormatter(); + } + } + } +} diff --git a/Effort.Extra.Tests/Properties/AssemblyInfo.cs b/Effort.Extra.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..ab5f231 --- /dev/null +++ b/Effort.Extra.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Effort.Extra.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("Effort.Extra.Tests")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ac0aa736-3f31-44c8-b668-0ef11be0e1fd")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Effort.Extra.Tests/packages.config b/Effort.Extra.Tests/packages.config new file mode 100644 index 0000000..e0ce53b --- /dev/null +++ b/Effort.Extra.Tests/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Effort.Extra.sln b/Effort.Extra.sln new file mode 100644 index 0000000..70b6894 --- /dev/null +++ b/Effort.Extra.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Effort.Extra", "Effort.Extra\Effort.Extra.csproj", "{0545D98F-BB8C-4689-8214-72CB8135A0EA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Effort.Extra.Tests", "Effort.Extra.Tests\Effort.Extra.Tests.csproj", "{064965F8-9D9C-4281-A4DB-2C14C33F52D4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0545D98F-BB8C-4689-8214-72CB8135A0EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0545D98F-BB8C-4689-8214-72CB8135A0EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0545D98F-BB8C-4689-8214-72CB8135A0EA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0545D98F-BB8C-4689-8214-72CB8135A0EA}.Release|Any CPU.Build.0 = Release|Any CPU + {064965F8-9D9C-4281-A4DB-2C14C33F52D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {064965F8-9D9C-4281-A4DB-2C14C33F52D4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {064965F8-9D9C-4281-A4DB-2C14C33F52D4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {064965F8-9D9C-4281-A4DB-2C14C33F52D4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Effort.Extra/App.config b/Effort.Extra/App.config new file mode 100644 index 0000000..8942ffe --- /dev/null +++ b/Effort.Extra/App.config @@ -0,0 +1,17 @@ + + + + +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/Effort.Extra/Effort.Extra.csproj b/Effort.Extra/Effort.Extra.csproj new file mode 100644 index 0000000..c07eb8d --- /dev/null +++ b/Effort.Extra/Effort.Extra.csproj @@ -0,0 +1,166 @@ + + + + + Debug + AnyCPU + {0545D98F-BB8C-4689-8214-72CB8135A0EA} + Library + Properties + Effort.Extra + Effort.Extra + v4.5 + 512 + 1 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + True + False + True + False + False + True + True + True + True + True + True + True + True + True + False + True + False + True + False + False + False + False + True + False + True + True + True + False + True + + + + + + + + True + False + False + True + Full + Build + 0 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + bin\Release\Effort.Extra.XML + True + False + True + False + False + True + True + True + True + True + True + True + True + True + False + True + False + True + False + False + False + False + True + False + True + True + True + False + True + + + + + + + + True + False + False + True + Full + Build + 0 + + + + False + ..\packages\Effort.EF6.1.1.4\lib\net45\Effort.dll + + + ..\packages\EntityFramework.6.0.0\lib\net45\EntityFramework.dll + + + ..\packages\EntityFramework.6.0.0\lib\net45\EntityFramework.SqlServer.dll + + + False + ..\packages\NMemory.1.0.1\lib\net45\NMemory.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Effort.Extra/Effort.Extra.nuspec b/Effort.Extra/Effort.Extra.nuspec new file mode 100644 index 0000000..d0d6e66 --- /dev/null +++ b/Effort.Extra/Effort.Extra.nuspec @@ -0,0 +1,24 @@ + + + + Effort.Extra + 1.1.0 + Effort Extra + Chris Rodgers + Chris Rodgers + false + Dynamic entity data loader for Effort. + Create database definitions in code and load them into the effort in-memory database at runtime. + + + + + + + + + + + + + \ No newline at end of file diff --git a/Effort.Extra/ObjectData.cs b/Effort.Extra/ObjectData.cs new file mode 100644 index 0000000..e9a5062 --- /dev/null +++ b/Effort.Extra/ObjectData.cs @@ -0,0 +1,100 @@ + +namespace Effort.Extra +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + + /// + /// An object used to create and access collections of entities. + /// + public class ObjectData + { + private readonly IDictionary tables = new Dictionary(); + private readonly Guid identifier = Guid.NewGuid(); + + internal virtual Guid Identifier { get { return identifier; } } + + /// + /// Returns the table specified by name. If a table with the specified name does not already exist, it will be created. + /// + /// The type of entity that the table should contain. + /// + /// Name of the table. + /// + /// If this value is null then the name of the entity will be used. + /// + /// + /// The existing table with the specified name, if it exists. Otherwise, a new table will be created. + /// + /// Thrown if the table exists, but the element type specified is incorrect. + /// + /// + /// + /// public class Fella + /// { + /// public string Name { get; set; } + /// } + /// ... + /// var data = new ObjectData(); + /// var table = data.Table<Fella>(); + /// table.Add(new Fella { Name = "Fred" }); + /// table.Add(new Fella { Name = "Jeff" }); + /// foreach (var fella in data.Table<Fella>()) + /// { + /// Debug.Print(fella.Name); + /// } + /// // prints: + /// // Fred + /// // Jeff + /// + /// + public IList Table(string tableName = null) + { + tableName = tableName ?? typeof(T).Name; + IEnumerable table; + if (!tables.TryGetValue(tableName, out table) || table == null) + { + table = new List(); + tables[tableName] = table; + } + if (table is IList) + { + return (IList)table; + } + var message = String.Format( + "A table with the name '{0}' already exists, but the element type is incorrect.\r\nExpected type: '{1}'\r\nActual type: '{2}'", + tableName, typeof(T).Name, table.GetType().GetGenericArguments()[0].Name); + throw new InvalidOperationException(message); + } + + internal bool HasTable(string tableName) + { + Contract.Requires(tableName != null); + Contract.Requires(!String.IsNullOrWhiteSpace(tableName)); + return tables.ContainsKey(tableName); + } + + internal Type TableType(string tableName) + { + Contract.Requires(tableName != null); + Contract.Requires(!String.IsNullOrWhiteSpace(tableName)); + IEnumerable table; + if (tables.TryGetValue(tableName, out table)) + { + return table.GetType().GetGenericArguments()[0]; + } + throw new InvalidOperationException(String.Format("No table with the name '{0}' defined.", tableName)); + } + + internal object GetTable(string tableName) + { + Contract.Requires(tableName != null); + Contract.Requires(!String.IsNullOrWhiteSpace(tableName)); + IEnumerable table; + tables.TryGetValue(tableName, out table); + return table; + } + } +} \ No newline at end of file diff --git a/Effort.Extra/ObjectDataCollection.cs b/Effort.Extra/ObjectDataCollection.cs new file mode 100644 index 0000000..57e6bfb --- /dev/null +++ b/Effort.Extra/ObjectDataCollection.cs @@ -0,0 +1,35 @@ + +namespace Effort.Extra +{ + using System; + using System.Collections.ObjectModel; + using System.Diagnostics.Contracts; + + /// + /// A keyed collection for ObjectData + /// + internal class ObjectDataCollection : KeyedCollection + { + protected override Guid GetKeyForItem(ObjectData item) + { + Contract.Requires(item != null); + return item.Identifier; + } + + /// + /// If the data already exists in the collection then it is updated, otherwise it is added. + /// + /// The data. + public void AddOrUpdate(ObjectData data) + { + if (Contains(data.Identifier)) Remove(data.Identifier); + Add(data); + } + + public bool TryGetValue(Guid key, out ObjectData data) + { + data = Contains(key) ? this[key] : null; + return data != null; + } + } +} \ No newline at end of file diff --git a/Effort.Extra/ObjectDataLoader.cs b/Effort.Extra/ObjectDataLoader.cs new file mode 100644 index 0000000..3424c1f --- /dev/null +++ b/Effort.Extra/ObjectDataLoader.cs @@ -0,0 +1,39 @@ + +namespace Effort.Extra +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using Effort.DataLoaders; + + public class ObjectDataLoader : IDataLoader + { + private static readonly ObjectDataCollection DataCollection = new ObjectDataCollection(); + + public ObjectDataLoader() { } + + public ObjectDataLoader(ObjectData data) + { + Contract.Requires(data != null); + Argument = data.Identifier.ToString(); + DataCollection.AddOrUpdate(data); + } + + public string Argument { get; set; } + + public ITableDataLoaderFactory CreateTableDataLoaderFactory() + { + Guid id; + if (Guid.TryParse(Argument, out id)) + { + ObjectData data; + if (DataCollection.TryGetValue(id, out data)) + { + return new ObjectDataLoaderFactory(data); + } + throw new KeyNotFoundException(String.Format("The key '{0}' was not found in the data collection.", id)); + } + throw new InvalidOperationException(String.Format("Unable to parse '{0}' as a guid.", Argument)); + } + } +} \ No newline at end of file diff --git a/Effort.Extra/ObjectDataLoaderFactory.cs b/Effort.Extra/ObjectDataLoaderFactory.cs new file mode 100644 index 0000000..46bda75 --- /dev/null +++ b/Effort.Extra/ObjectDataLoaderFactory.cs @@ -0,0 +1,36 @@ + +namespace Effort.Extra +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using Effort.DataLoaders; + + internal class ObjectDataLoaderFactory : ITableDataLoaderFactory + { + private static readonly Type LoaderType = typeof(ObjectTableDataLoader<>); + private readonly ObjectData data; + + public ObjectDataLoaderFactory(ObjectData data) + { + Contract.Requires(data != null); + this.data = data; + } + + public void Dispose() { } + + public ITableDataLoader CreateTableDataLoader(TableDescription table) + { + Contract.Requires(table != null); + if (!data.HasTable(table.Name)) return new EmptyTableDataLoader(); + var entityType = data.TableType(table.Name); + var type = LoaderType.MakeGenericType(entityType); + var constructor = type.GetConstructor(new[] + { + typeof (TableDescription), + typeof (IEnumerable<>).MakeGenericType(entityType) + }); + return (ITableDataLoader)constructor.Invoke(new[] { table, data.GetTable(table.Name) }); + } + } +} diff --git a/Effort.Extra/ObjectTableDataLoader`1.cs b/Effort.Extra/ObjectTableDataLoader`1.cs new file mode 100644 index 0000000..77b1644 --- /dev/null +++ b/Effort.Extra/ObjectTableDataLoader`1.cs @@ -0,0 +1,41 @@ + +namespace Effort.Extra +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Linq.Expressions; + using Effort.DataLoaders; + + internal class ObjectTableDataLoader : ITableDataLoader + { + private readonly TableDescription table; + private readonly IEnumerable entities; + private readonly Lazy> formatter; + + public ObjectTableDataLoader(TableDescription table, IEnumerable entities) + { + Contract.Requires(table != null); + Contract.Requires(entities != null); + this.table = table; + this.entities = entities; + formatter = new Lazy>(CreateFormatter); + } + + protected Func CreateFormatter() + { + var type = typeof(T); + var param = Expression.Parameter(type, "x"); + var initialisers = table.Columns + .Select(c => Expression.TypeAs(Expression.Property(param, type.GetProperty(c.Name)), typeof(object))); + var newArray = Expression.NewArrayInit(typeof(object), initialisers); + return Expression.Lambda>(newArray, param).Compile(); + } + + public IEnumerable GetData() + { + return entities.Select(formatter.Value); + } + } +} diff --git a/Effort.Extra/Properties/AssemblyInfo.cs b/Effort.Extra/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..2d69a28 --- /dev/null +++ b/Effort.Extra/Properties/AssemblyInfo.cs @@ -0,0 +1,16 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Effort.Extra")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Effort.Extra")] +[assembly: AssemblyCopyright("Copyright © Chris Rodgers 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("fadfc421-9ef5-4198-8eb0-fbbfcbae1167")] +[assembly: AssemblyVersion("1.1.0")] +[assembly: InternalsVisibleTo("Effort.Extra.Tests")] \ No newline at end of file diff --git a/Effort.Extra/packages.config b/Effort.Extra/packages.config new file mode 100644 index 0000000..580cd50 --- /dev/null +++ b/Effort.Extra/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file