diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..bdaa5ba9827 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,50 @@ +*.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 + +*.jpg binary +*.png binary +*.gif binary + +*.cs text=auto diff=csharp +*.vb text=auto +*.resx text=auto +*.c text=auto +*.cpp text=auto +*.cxx text=auto +*.h text=auto +*.hxx text=auto +*.py text=auto +*.rb text=auto +*.java text=auto +*.html text=auto +*.htm text=auto +*.css text=auto +*.scss text=auto +*.sass text=auto +*.less text=auto +*.js text=auto +*.lisp text=auto +*.clj text=auto +*.sql text=auto +*.php text=auto +*.lua text=auto +*.m text=auto +*.asm text=auto +*.erl text=auto +*.fs text=auto +*.fsx text=auto +*.hs text=auto + +*.csproj text=auto +*.vbproj text=auto +*.fsproj text=auto +*.dbproj text=auto +*.sln text=auto eol=crlf diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..5cf7c13c7e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +[Oo]bj/ +[Bb]in/ +*.xap +*.user +/TestResults +*.vspscc +*.vssscc +*.suo +*.cache +*.docstates +_ReSharper.* +*.csproj.user +*[Rr]e[Ss]harper.user +_ReSharper.*/ +packages/* +artifacts/* +msbuild.log +PublishProfiles/ +*.psess +*.vsp +*.pidb +*.userprefs +*DS_Store +*.ncrunchsolution +*.log +*.vspx +/.symbols +nuget.exe +build/ +*net45.csproj +*k10.csproj \ No newline at end of file diff --git a/Data.sln b/Data.sln new file mode 100644 index 00000000000..58004f24e19 --- /dev/null +++ b/Data.sln @@ -0,0 +1,117 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.30110.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.Core", "src\Microsoft.Data.Core\Microsoft.Data.Core.csproj", "{6A531BB3-DF57-4743-AF3B-4BE431B67DE6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.InMemory", "src\Microsoft.Data.InMemory\Microsoft.Data.InMemory.csproj", "{01F860AB-80DD-4AB4-B495-D5B20A493E60}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.Core.UnitTest", "test\Microsoft.Data.Tests.Unit\Microsoft.Data.Core.UnitTest.csproj", "{FB272D58-D5FD-41D1-9E02-BCE17FD97F0E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.Core.FunctionalTest", "test\Microsoft.Data.Tests.Functional\Microsoft.Data.Core.FunctionalTest.csproj", "{A08C9073-CB43-409E-9A5E-4BE2CA687FE3}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{35BED7DE-CA6F-41EA-91E3-25CA7295C7B9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{547020C0-976F-4542-81CE-7FD465595614}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.Relational", "src\Microsoft.Data.Relational\Microsoft.Data.Relational.csproj", "{8C045113-8E76-4910-9C61-95BC82BB3003}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.SQLite", "src\Microsoft.Data.SQLite\Microsoft.Data.SQLite.csproj", "{33D71368-9175-4A74-831E-11ACE6D815EE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.SqlServer", "src\Microsoft.Data.SqlServer\Microsoft.Data.SqlServer.csproj", "{D30CDA1E-CD18-42CE-934F-F33162F1B6EC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6A531BB3-DF57-4743-AF3B-4BE431B67DE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6A531BB3-DF57-4743-AF3B-4BE431B67DE6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6A531BB3-DF57-4743-AF3B-4BE431B67DE6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {6A531BB3-DF57-4743-AF3B-4BE431B67DE6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {6A531BB3-DF57-4743-AF3B-4BE431B67DE6}.Debug|x86.ActiveCfg = Debug|Any CPU + {6A531BB3-DF57-4743-AF3B-4BE431B67DE6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6A531BB3-DF57-4743-AF3B-4BE431B67DE6}.Release|Any CPU.Build.0 = Release|Any CPU + {6A531BB3-DF57-4743-AF3B-4BE431B67DE6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {6A531BB3-DF57-4743-AF3B-4BE431B67DE6}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {6A531BB3-DF57-4743-AF3B-4BE431B67DE6}.Release|x86.ActiveCfg = Release|Any CPU + {01F860AB-80DD-4AB4-B495-D5B20A493E60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {01F860AB-80DD-4AB4-B495-D5B20A493E60}.Debug|Any CPU.Build.0 = Debug|Any CPU + {01F860AB-80DD-4AB4-B495-D5B20A493E60}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {01F860AB-80DD-4AB4-B495-D5B20A493E60}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {01F860AB-80DD-4AB4-B495-D5B20A493E60}.Debug|x86.ActiveCfg = Debug|Any CPU + {01F860AB-80DD-4AB4-B495-D5B20A493E60}.Release|Any CPU.ActiveCfg = Release|Any CPU + {01F860AB-80DD-4AB4-B495-D5B20A493E60}.Release|Any CPU.Build.0 = Release|Any CPU + {01F860AB-80DD-4AB4-B495-D5B20A493E60}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {01F860AB-80DD-4AB4-B495-D5B20A493E60}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {01F860AB-80DD-4AB4-B495-D5B20A493E60}.Release|x86.ActiveCfg = Release|Any CPU + {FB272D58-D5FD-41D1-9E02-BCE17FD97F0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FB272D58-D5FD-41D1-9E02-BCE17FD97F0E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FB272D58-D5FD-41D1-9E02-BCE17FD97F0E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {FB272D58-D5FD-41D1-9E02-BCE17FD97F0E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {FB272D58-D5FD-41D1-9E02-BCE17FD97F0E}.Debug|x86.ActiveCfg = Debug|Any CPU + {FB272D58-D5FD-41D1-9E02-BCE17FD97F0E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FB272D58-D5FD-41D1-9E02-BCE17FD97F0E}.Release|Any CPU.Build.0 = Release|Any CPU + {FB272D58-D5FD-41D1-9E02-BCE17FD97F0E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {FB272D58-D5FD-41D1-9E02-BCE17FD97F0E}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {FB272D58-D5FD-41D1-9E02-BCE17FD97F0E}.Release|x86.ActiveCfg = Release|Any CPU + {A08C9073-CB43-409E-9A5E-4BE2CA687FE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A08C9073-CB43-409E-9A5E-4BE2CA687FE3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A08C9073-CB43-409E-9A5E-4BE2CA687FE3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {A08C9073-CB43-409E-9A5E-4BE2CA687FE3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {A08C9073-CB43-409E-9A5E-4BE2CA687FE3}.Debug|x86.ActiveCfg = Debug|Any CPU + {A08C9073-CB43-409E-9A5E-4BE2CA687FE3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A08C9073-CB43-409E-9A5E-4BE2CA687FE3}.Release|Any CPU.Build.0 = Release|Any CPU + {A08C9073-CB43-409E-9A5E-4BE2CA687FE3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {A08C9073-CB43-409E-9A5E-4BE2CA687FE3}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {A08C9073-CB43-409E-9A5E-4BE2CA687FE3}.Release|x86.ActiveCfg = Release|Any CPU + {8C045113-8E76-4910-9C61-95BC82BB3003}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C045113-8E76-4910-9C61-95BC82BB3003}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C045113-8E76-4910-9C61-95BC82BB3003}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {8C045113-8E76-4910-9C61-95BC82BB3003}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {8C045113-8E76-4910-9C61-95BC82BB3003}.Debug|x86.ActiveCfg = Debug|Any CPU + {8C045113-8E76-4910-9C61-95BC82BB3003}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8C045113-8E76-4910-9C61-95BC82BB3003}.Release|Any CPU.Build.0 = Release|Any CPU + {8C045113-8E76-4910-9C61-95BC82BB3003}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {8C045113-8E76-4910-9C61-95BC82BB3003}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {8C045113-8E76-4910-9C61-95BC82BB3003}.Release|x86.ActiveCfg = Release|Any CPU + {33D71368-9175-4A74-831E-11ACE6D815EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33D71368-9175-4A74-831E-11ACE6D815EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33D71368-9175-4A74-831E-11ACE6D815EE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {33D71368-9175-4A74-831E-11ACE6D815EE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {33D71368-9175-4A74-831E-11ACE6D815EE}.Debug|x86.ActiveCfg = Debug|Any CPU + {33D71368-9175-4A74-831E-11ACE6D815EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33D71368-9175-4A74-831E-11ACE6D815EE}.Release|Any CPU.Build.0 = Release|Any CPU + {33D71368-9175-4A74-831E-11ACE6D815EE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {33D71368-9175-4A74-831E-11ACE6D815EE}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {33D71368-9175-4A74-831E-11ACE6D815EE}.Release|x86.ActiveCfg = Release|Any CPU + {D30CDA1E-CD18-42CE-934F-F33162F1B6EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D30CDA1E-CD18-42CE-934F-F33162F1B6EC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D30CDA1E-CD18-42CE-934F-F33162F1B6EC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {D30CDA1E-CD18-42CE-934F-F33162F1B6EC}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {D30CDA1E-CD18-42CE-934F-F33162F1B6EC}.Debug|x86.ActiveCfg = Debug|Any CPU + {D30CDA1E-CD18-42CE-934F-F33162F1B6EC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D30CDA1E-CD18-42CE-934F-F33162F1B6EC}.Release|Any CPU.Build.0 = Release|Any CPU + {D30CDA1E-CD18-42CE-934F-F33162F1B6EC}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {D30CDA1E-CD18-42CE-934F-F33162F1B6EC}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {D30CDA1E-CD18-42CE-934F-F33162F1B6EC}.Release|x86.ActiveCfg = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {01F860AB-80DD-4AB4-B495-D5B20A493E60} = {35BED7DE-CA6F-41EA-91E3-25CA7295C7B9} + {6A531BB3-DF57-4743-AF3B-4BE431B67DE6} = {35BED7DE-CA6F-41EA-91E3-25CA7295C7B9} + {8C045113-8E76-4910-9C61-95BC82BB3003} = {35BED7DE-CA6F-41EA-91E3-25CA7295C7B9} + {33D71368-9175-4A74-831E-11ACE6D815EE} = {35BED7DE-CA6F-41EA-91E3-25CA7295C7B9} + {D30CDA1E-CD18-42CE-934F-F33162F1B6EC} = {35BED7DE-CA6F-41EA-91E3-25CA7295C7B9} + {A08C9073-CB43-409E-9A5E-4BE2CA687FE3} = {547020C0-976F-4542-81CE-7FD465595614} + {FB272D58-D5FD-41D1-9E02-BCE17FD97F0E} = {547020C0-976F-4542-81CE-7FD465595614} + EndGlobalSection +EndGlobal diff --git a/Data.sln.DotSettings b/Data.sln.DotSettings new file mode 100644 index 00000000000..c954f98885a --- /dev/null +++ b/Data.sln.DotSettings @@ -0,0 +1,67 @@ + + + <?xml version="1.0" encoding="utf-16"?><Profile name="EntityFramework"><HtmlReformatCode>True</HtmlReformatCode><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSRemoveCodeRedundancies>True</CSRemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSUseVar><BehavourStyle>CAN_CHANGE_TO_IMPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_IMPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_IMPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><XMLReformatCode>True</XMLReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSharpFormatDocComments>True</CSharpFormatDocComments></Profile> + + EntityFramework + EntityFramework + False + False + False + SEPARATE + True + True + True + ALWAYS_ADD + ALWAYS_ADD + ALWAYS_ADD + ALWAYS_ADD + ALWAYS_ADD + ALWAYS_ADD + True + 1 + 1 + True + False + False + False + True + LINE_BREAK + False + True + False + True + True + True + True + True + 140 + CHOP_ALWAYS + True + + True + False + True + True + True + Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + + True + True + True + Side by side + Side by side + False + False + False + True + False + False + True + False + False + True + $object$_On$event$ + <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> + $object$_On$event$ + True + True \ No newline at end of file diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 00000000000..ab583b0ff77 --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.cmd b/build.cmd new file mode 100644 index 00000000000..c3b24620191 --- /dev/null +++ b/build.cmd @@ -0,0 +1,16 @@ +@echo off +cd %~dp0 + +IF EXIST .nuget\NuGet.exe goto restore +echo Downloading latest version of NuGet.exe... +md .nuget +@powershell -NoProfile -ExecutionPolicy unrestricted -Command "$ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest 'https://www.nuget.org/nuget.exe' -OutFile '.nuget\NuGet.exe'" + +:restore +IF EXIST build goto run +.nuget\NuGet.exe install KoreBuild -ExcludeVersion -o packages -nocache -pre +xcopy packages\KoreBuild\build build\ /Y +.nuget\NuGet.exe install Sake -version 0.2 -o packages + +:run +packages\Sake.0.2\tools\Sake.exe -I build -f makefile.shade %* diff --git a/makefile.shade b/makefile.shade new file mode 100644 index 00000000000..6357ea2841d --- /dev/null +++ b/makefile.shade @@ -0,0 +1,7 @@ + +var VERSION='0.1' +var FULL_VERSION='0.1' +var AUTHORS='Microsoft' + +use-standard-lifecycle +k-standard-goals diff --git a/src/Microsoft.Data.Core/Metadata/Annotation.cs b/src/Microsoft.Data.Core/Metadata/Annotation.cs new file mode 100644 index 00000000000..2fd48debc95 --- /dev/null +++ b/src/Microsoft.Data.Core/Metadata/Annotation.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Metadata +{ + using JetBrains.Annotations; + using Microsoft.Data.Core.Utilities; + + public class Annotation : MetadataBase + { + private readonly object _value; + + public Annotation([NotNull] string name, object value) + : base(name) + { + Check.NotNull(value, "value"); + + _value = value; + } + + public virtual object Value + { + get { return _value; } + } + } +} diff --git a/src/Microsoft.Data.Core/Metadata/Entity.cs b/src/Microsoft.Data.Core/Metadata/Entity.cs new file mode 100644 index 00000000000..3c94343f0fb --- /dev/null +++ b/src/Microsoft.Data.Core/Metadata/Entity.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Metadata +{ + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Linq; + using System.Reflection; + using JetBrains.Annotations; + using Microsoft.Data.Core.Utilities; + + public class Entity : MetadataBase + { + private readonly Type _type; + + private readonly LazyRef> _properties + = new LazyRef>(() => ImmutableDictionary.Empty); + + private readonly LazyRef> _keyProperties + = new LazyRef>(() => ImmutableList.Empty); + + public Entity([NotNull] Type type) + : base(Check.NotNull(type, "type", t => t.Name)) + { + _type = type; + } + + public virtual Type Type + { + get { return _type; } + } + + public virtual IEnumerable Key + { + get + { + return _keyProperties.HasValue + ? _keyProperties.Value + : Enumerable.Empty(); + } + [param: NotNull] + set + { + Check.NotNull(value, "value"); + + _keyProperties.ExchangeValue(l => l.Clear().AddRange(value)); + _properties.ExchangeValue(ps => ps.SetItems(value.ToDictionary(p => p.PropertyInfo))); + } + } + + public virtual void AddProperty([NotNull] Property property) + { + Check.NotNull(property, "property"); + + _properties.ExchangeValue(l => l.Add(property.PropertyInfo, property)); + } + + public virtual void RemoveProperty([NotNull] Property property) + { + Check.NotNull(property, "property"); + + if (_properties.HasValue) + { + _properties.ExchangeValue(l => l.Remove(property.PropertyInfo)); + + if (_keyProperties.HasValue) + { + _keyProperties.ExchangeValue(l => l.Remove(property)); + } + } + } + + public virtual Property Property([NotNull] PropertyInfo propertyInfo) + { + Check.NotNull(propertyInfo, "propertyInfo"); + + Property property; + return _properties.HasValue + && _properties.Value.TryGetValue(propertyInfo, out property) + ? property + : null; + } + + public virtual IEnumerable Properties + { + get + { + return _properties.HasValue + ? _properties.Value.Values.OrderByOrdinal(e => e.Name) + : Enumerable.Empty(); + } + } + } +} diff --git a/src/Microsoft.Data.Core/Metadata/MetadataBase.cs b/src/Microsoft.Data.Core/Metadata/MetadataBase.cs new file mode 100644 index 00000000000..2e045092719 --- /dev/null +++ b/src/Microsoft.Data.Core/Metadata/MetadataBase.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Metadata +{ + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Diagnostics; + using System.Linq; + using JetBrains.Annotations; + using Microsoft.Data.Core.Utilities; + + [DebuggerDisplay("{Name}")] + public abstract class MetadataBase + { + private readonly string _name; + + private string _storageName; + + private readonly LazyRef> _annotations + = new LazyRef>(() => ImmutableDictionary.Empty); + + protected MetadataBase() + { + } + + protected MetadataBase([NotNull] string name) + { + Check.NotEmpty(name, "name"); + + _name = name; + } + + public virtual string Name + { + get { return _name; } + } + + public virtual string StorageName + { + get { return _storageName ?? Name; } + [param: NotNull] + set + { + Check.NotEmpty(value, "value"); + + _storageName = value; + } + } + + public virtual void AddAnnotation([NotNull] Annotation annotation) + { + Check.NotNull(annotation, "annotation"); + + _annotations.ExchangeValue(d => d.Add(annotation.Name, annotation)); + } + + public virtual void RemoveAnnotation([NotNull] Annotation annotation) + { + Check.NotNull(annotation, "annotation"); + + _annotations.ExchangeValue(l => l.Remove(annotation.Name)); + } + + public virtual object this[[NotNull] string annotation] + { + get + { + Check.NotEmpty(annotation, "annotation"); + + Annotation value; + return _annotations.HasValue + && _annotations.Value.TryGetValue(annotation, out value) + ? value.Value + : null; + } + [param: NotNull] + set + { + Check.NotEmpty(annotation, "annotation"); + Check.NotNull(value, "value"); + + _annotations.ExchangeValue(l => l.Remove(annotation)); + + AddAnnotation(new Annotation(annotation, value)); + } + } + + public virtual IEnumerable Annotations + { + get + { + return _annotations.HasValue + ? _annotations.Value.Values.OrderByOrdinal(e => e.Name) + : Enumerable.Empty(); + } + } + } +} diff --git a/src/Microsoft.Data.Core/Metadata/Model.cs b/src/Microsoft.Data.Core/Metadata/Model.cs new file mode 100644 index 00000000000..a271824cad7 --- /dev/null +++ b/src/Microsoft.Data.Core/Metadata/Model.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Metadata +{ + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Linq; + using JetBrains.Annotations; + using Microsoft.Data.Core.Utilities; + + public class Model : MetadataBase + { + private readonly LazyRef> _entities + = new LazyRef>(() => ImmutableDictionary.Empty); + + public virtual void AddEntity([NotNull] Entity entity) + { + Check.NotNull(entity, "entity"); + + _entities.ExchangeValue(d => d.Add(entity.Type, entity)); + } + + public virtual void RemoveEntity([NotNull] Entity entity) + { + Check.NotNull(entity, "entity"); + + _entities.ExchangeValue(l => l.Remove(entity.Type)); + } + + public virtual Entity Entity([NotNull] object @object) + { + Check.NotNull(@object, "object"); + + return Entity(@object.GetType()); + } + + public virtual Entity Entity([NotNull] Type type) + { + Check.NotNull(type, "type"); + + Entity value; + return _entities.HasValue + && _entities.Value.TryGetValue(type, out value) + ? value + : null; + } + + public virtual IEnumerable Entities + { + get + { + return _entities.HasValue + ? _entities.Value.Values.OrderByOrdinal(e => e.Name) + : Enumerable.Empty(); + } + } + } +} diff --git a/src/Microsoft.Data.Core/Metadata/ModelBuilder.cs b/src/Microsoft.Data.Core/Metadata/ModelBuilder.cs new file mode 100644 index 00000000000..706fd1ec70f --- /dev/null +++ b/src/Microsoft.Data.Core/Metadata/ModelBuilder.cs @@ -0,0 +1,147 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Metadata +{ + using System; + using System.Linq; + using System.Linq.Expressions; + using JetBrains.Annotations; + using Microsoft.Data.Core.Utilities; + + public class ModelBuilder + { + private readonly Model _model; + + public ModelBuilder([NotNull] Model model) + { + Check.NotNull(model, "model"); + + _model = model; + } + + public virtual EntityBuilder Entity() + { + var type = typeof(T); + var entity = _model.Entity(type); + + if (entity == null) + { + _model.AddEntity(entity = new Entity(type)); + } + + return new EntityBuilder(entity); + } + + public virtual ModelBuilder Annotation([NotNull] string annotation, [NotNull] object value) + { + Check.NotEmpty(annotation, "annotation"); + Check.NotNull(value, "value"); + + _model[annotation] = value; + + return this; + } + + public class EntityBuilder + { + private readonly Entity _entity; + + internal EntityBuilder() + { + } + + internal EntityBuilder(Entity entity) + { + DebugCheck.NotNull(entity); + + _entity = entity; + } + + public EntityBuilder Key([NotNull] Expression> keyExpression) + { + Check.NotNull(keyExpression, "keyExpression"); + + var propertyInfos = keyExpression.GetPropertyAccessList(); + + _entity.Key + = propertyInfos + .Select(pi => _entity.Property(pi) ?? new Property(pi)); + + return this; + } + + public EntityBuilder Annotation([NotNull] string annotation, [NotNull] object value) + { + Check.NotEmpty(annotation, "annotation"); + Check.NotNull(value, "value"); + + _entity[annotation] = value; + + return this; + } + + public EntityBuilder Properties([NotNull] Action propertiesBuilder) + { + Check.NotNull(propertiesBuilder, "propertiesBuilder"); + + propertiesBuilder(new PropertiesBuilder(_entity)); + + return this; + } + + public class PropertiesBuilder + { + private readonly Entity _entity; + + internal PropertiesBuilder() + { + } + + internal PropertiesBuilder(Entity entity) + { + _entity = entity; + } + + public virtual PropertyBuilder Property([NotNull] Expression> propertyExpression) + { + var propertyInfo = propertyExpression.GetPropertyAccess(); + + var property = _entity.Property(propertyInfo); + + if (property == null) + { + _entity.AddProperty(property = new Property(propertyInfo)); + } + + return new PropertyBuilder(property); + } + + public class PropertyBuilder + { + private readonly Property _property; + + internal PropertyBuilder() + { + } + + internal PropertyBuilder(Property property) + { + DebugCheck.NotNull(property); + + _property = property; + } + + public PropertyBuilder Annotation([NotNull] string annotation, [NotNull] object value) + { + Check.NotEmpty(annotation, "annotation"); + Check.NotNull(value, "value"); + + _property[annotation] = value; + + return this; + } + } + } + } + } +} diff --git a/src/Microsoft.Data.Core/Metadata/Property.cs b/src/Microsoft.Data.Core/Metadata/Property.cs new file mode 100644 index 00000000000..f5d14deb70a --- /dev/null +++ b/src/Microsoft.Data.Core/Metadata/Property.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Metadata +{ + using System.Reflection; + using JetBrains.Annotations; + using Microsoft.Data.Core.Utilities; + + public class Property : MetadataBase + { + private readonly PropertyInfo _propertyInfo; + + public Property([NotNull] PropertyInfo propertyInfo) + : base(Check.NotNull(propertyInfo, "propertyInfo", p => p.Name)) + { + _propertyInfo = propertyInfo; + } + + public virtual PropertyInfo PropertyInfo + { + get { return _propertyInfo; } + } + } +} diff --git a/src/Microsoft.Data.Core/Microsoft.Data.Core.csproj b/src/Microsoft.Data.Core/Microsoft.Data.Core.csproj new file mode 100644 index 00000000000..1838aab418d --- /dev/null +++ b/src/Microsoft.Data.Core/Microsoft.Data.Core.csproj @@ -0,0 +1,94 @@ + + + + + 12.0 + Debug + AnyCPU + {6A531BB3-DF57-4743-AF3B-4BE431B67DE6} + Library + Properties + Microsoft.Data.Core + Microsoft.Data.Core + v4.6 + Profile44 + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + ExtendedDesignGuidelineRules.ruleset + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + true + + + + + ..\..\packages\Microsoft.Bcl.Immutable.1.0.30\lib\portable-net45+win8+wp8\System.Collections.Immutable.dll + + + + + + + + + + + Code + + + + + + Code + + + + + + + + + + + + True + True + Resources.tt + + + TextTemplatingFileGenerator + Resources.cs + + + Designer + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.Data.Core/Properties/InternalsVisibleTo.cs b/src/Microsoft.Data.Core/Properties/InternalsVisibleTo.cs new file mode 100644 index 00000000000..470693c2b90 --- /dev/null +++ b/src/Microsoft.Data.Core/Properties/InternalsVisibleTo.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Runtime.CompilerServices; + +#if !INTERNALS_INVISIBLE + +[assembly: InternalsVisibleTo("Microsoft.Data.Core.UnitTest")] + +// for Moq + +[assembly: + InternalsVisibleTo( + "DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" + )] + +#endif diff --git a/src/Microsoft.Data.Core/Resources/Resources.cs b/src/Microsoft.Data.Core/Resources/Resources.cs new file mode 100644 index 00000000000..6bf9dd284bb --- /dev/null +++ b/src/Microsoft.Data.Core/Resources/Resources.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Resources +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Strings + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.Data.Core.Resources.Resources", typeof(Strings).GetTypeInfo().Assembly); + + /// + /// "The argument '{0}' cannot be null, empty or contain only white space." + /// + internal static string ArgumentIsNullOrWhitespace(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("ArgumentIsNullOrWhitespace"), p0); + } + + /// + /// "The properties expression '{0}' is not valid. The expression should represent a property: C#: 't => t.MyProperty' + /// VB.Net: 'Function(t) t.MyProperty'. When specifying multiple properties use an anonymous type: C#: 't => new {{ + /// t.MyProperty1, t.MyProperty2 }}' VB.Net: 'Function(t) New With {{ t.MyProperty1, t.MyProperty2 }}'." + /// + internal static string InvalidPropertiesExpression(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("InvalidPropertiesExpression"), p0); + } + + /// + /// "The expression '{0}' is not a valid property expression. The expression should represent a property: C#: 't => + /// t.MyProperty' VB.Net: 'Function(t) t.MyProperty'." + /// + internal static string InvalidPropertyExpression(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("InvalidPropertyExpression"), p0); + } + + private static string GetString(string name) + { + return _resourceManager.GetString(name) + ?? "ERROR: Resource '" + name + "' NOT FOUND!"; + } + } +} diff --git a/src/Microsoft.Data.Core/Resources/Resources.resx b/src/Microsoft.Data.Core/Resources/Resources.resx new file mode 100644 index 00000000000..dd1d565e73a --- /dev/null +++ b/src/Microsoft.Data.Core/Resources/Resources.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The argument '{0}' cannot be null, empty or contain only white space. + + + The properties expression '{0}' is not valid. The expression should represent a property: C#: 't => t.MyProperty' VB.Net: 'Function(t) t.MyProperty'. When specifying multiple properties use an anonymous type: C#: 't => new {{ t.MyProperty1, t.MyProperty2 }}' VB.Net: 'Function(t) New With {{ t.MyProperty1, t.MyProperty2 }}'. + + + The expression '{0}' is not a valid property expression. The expression should represent a property: C#: 't => t.MyProperty' VB.Net: 'Function(t) t.MyProperty'. + + \ No newline at end of file diff --git a/src/Microsoft.Data.Core/Resources/Resources.tt b/src/Microsoft.Data.Core/Resources/Resources.tt new file mode 100644 index 00000000000..31efc795b62 --- /dev/null +++ b/src/Microsoft.Data.Core/Resources/Resources.tt @@ -0,0 +1,91 @@ +<#@ template debug="true" hostspecific="true" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ assembly name="System.Windows.Forms" #> +<#@ import namespace="System" #> +<#@ import namespace="System.Collections" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="System.IO" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Resources" #> +<#@ import namespace="System.Text.RegularExpressions" #> +<#@ output extension=".cs" #> +<# + +var parameterMatcher = new Regex(@"\{(\d)\}"); +var lines = new List>(); + +using (var resxReader = new ResXResourceReader(Path.ChangeExtension(Host.TemplateFile, "resx"))) +{ + resxReader.UseResXDataNodes = true; + + foreach (DictionaryEntry entry in resxReader) + { + var node = (ResXDataNode)entry.Value; + var value = (string)node.GetValue((System.ComponentModel.Design.ITypeResolutionService)null); + + var matchedArgs + = parameterMatcher.Matches(value) + .Cast() + .Select(m => Convert.ToInt32(m.Groups[1].Value)) + .ToArray(); + + var argGenerator + = new object[matchedArgs.Any() ? matchedArgs.Max() + 1 : 0]; + + lines.Add(Tuple.Create( + node.Name, + value, + node.Comment.StartsWith("## ExceptionType=") ? node.Comment.Substring(17) : null, + argGenerator.Any(), + string.Join(", ", argGenerator.Select((_, i) => "p" + i)), + "(" + string.Join(", ", argGenerator.Select((_, i) => "object p" + i)) + ")" + )); + } +} + +string outputNamespace = Host.ResolveParameterValue("directiveId", "namespaceDirectiveProcessor", "namespaceHint") ?? string.Empty; +#> +// + +namespace <#= outputNamespace #> +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Strings + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.Data.Core.Resources.Resources", typeof(Strings).GetTypeInfo().Assembly); +<# + foreach (var line in lines) + { + #> + + /// + /// "<#= line.Item2 #>" + /// + internal static string <#= line.Item1 #><#= line.Item4 ? line.Item6 : string.Empty #> + { + <# + if (!line.Item4) + { + #>get { return GetString("<#= line.Item1 #>"); } +<# + } + else + { + #>return string.Format(CultureInfo.CurrentCulture, GetString("<#= line.Item1 #>"), <#= line.Item5 #>); +<# + }#> + } +<# + }#> + + private static string GetString(string name) + { + return _resourceManager.GetString(name) + ?? "ERROR: Resource '" + name + "' NOT FOUND!"; + } + } +} diff --git a/src/Microsoft.Data.Core/Utilities/Check.cs b/src/Microsoft.Data.Core/Utilities/Check.cs new file mode 100644 index 00000000000..eb6777a7e51 --- /dev/null +++ b/src/Microsoft.Data.Core/Utilities/Check.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Utilities +{ + using System; + using System.Diagnostics; + using JetBrains.Annotations; + using Microsoft.Data.Core.Resources; + + [DebuggerStepThrough] + public static class Check + { + public static void NotNull(object value, [InvokerParameterName] [NotNull] string parameterName) + { + NotEmpty(parameterName, "parameterName"); + + if (value == null) + { + throw new ArgumentNullException(parameterName); + } + } + + public static S NotNull(T value, [InvokerParameterName] [NotNull] string parameterName, Func result) + { + NotNull(value, parameterName); + + return result(value); + } + +// public static T Inline(T value, Action checker) +// where T : class +// { +// checker(); +// +// return value; +// } + + public static string NotEmpty(string value, [InvokerParameterName] [NotNull] string parameterName) + { + if (string.IsNullOrWhiteSpace(parameterName)) + { + throw new ArgumentException(Strings.ArgumentIsNullOrWhitespace("parameterName")); + } + + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException(Strings.ArgumentIsNullOrWhitespace(parameterName)); + } + + return value; + } + } +} diff --git a/src/Microsoft.Data.Core/Utilities/CodeAnnotations.cs b/src/Microsoft.Data.Core/Utilities/CodeAnnotations.cs new file mode 100644 index 00000000000..66ef0723fe1 --- /dev/null +++ b/src/Microsoft.Data.Core/Utilities/CodeAnnotations.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace JetBrains.Annotations +{ + using System; + + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | + AttributeTargets.Property | AttributeTargets.Delegate | + AttributeTargets.Field, AllowMultiple = false, Inherited = true)] + public sealed class NotNullAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] + public sealed class InvokerParameterNameAttribute : Attribute + { + } +} diff --git a/src/Microsoft.Data.Core/Utilities/DebugCheck.cs b/src/Microsoft.Data.Core/Utilities/DebugCheck.cs new file mode 100644 index 00000000000..806b3d7a211 --- /dev/null +++ b/src/Microsoft.Data.Core/Utilities/DebugCheck.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Utilities +{ + using System.Diagnostics; + + [DebuggerStepThrough] + public static class DebugCheck + { + [Conditional("DEBUG")] + public static void NotNull(object value) + { + Debug.Assert(value != null); + } + + [Conditional("DEBUG")] + public static void NotEmpty(string value) + { + Debug.Assert(!string.IsNullOrWhiteSpace(value)); + } + } +} diff --git a/src/Microsoft.Data.Core/Utilities/EnumerableExtensions.cs b/src/Microsoft.Data.Core/Utilities/EnumerableExtensions.cs new file mode 100644 index 00000000000..16dd9748dcd --- /dev/null +++ b/src/Microsoft.Data.Core/Utilities/EnumerableExtensions.cs @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Utilities +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Threading.Tasks; + using JetBrains.Annotations; + + [DebuggerStepThrough] + public static class EnumerableExtensions + { + public static IOrderedEnumerable OrderByOrdinal( + this IEnumerable source, Func keySelector) + { + Check.NotNull(source, "source"); + Check.NotNull(keySelector, "keySelector"); + + return source.OrderBy(keySelector, StringComparer.Ordinal); + } + + public static IEnumerable Distinct( + [NotNull] this IEnumerable source, [NotNull] Func comparer) + where T : class + { + Check.NotNull(source, "source"); + Check.NotNull(comparer, "comparer"); + + return source.Distinct(new DynamicEqualityComparer(comparer)); + } + + private sealed class DynamicEqualityComparer : IEqualityComparer + where T : class + { + private readonly Func _func; + + public DynamicEqualityComparer(Func func) + { + DebugCheck.NotNull(func); + + _func = func; + } + + public bool Equals(T x, T y) + { + return _func(x, y); + } + + public int GetHashCode(T obj) + { + return 0; // force Equals + } + } + + public static string Join([NotNull] this IEnumerable source, string separator = ", ") + { + Check.NotNull(source, "source"); + + return string.Join(separator, source); + } + + public static async Task> SelectAsync( + [NotNull] this IEnumerable source, [NotNull] Func> selector) + { + Check.NotNull(source, "source"); + Check.NotNull(selector, "selector"); + + var results = Enumerable.Empty(); + + foreach (var item in source) + { + results = results.Concat(new[] { await selector(item) }); + } + + return results; + } + + public static async Task> SelectManyAsync( + [NotNull] this IEnumerable source, [NotNull] Func>> selector) + { + Check.NotNull(source, "source"); + Check.NotNull(selector, "selector"); + + var results = Enumerable.Empty(); + + foreach (var item in source) + { + results = results.Concat(await selector(item)); + } + + return results; + } + + public static async Task> WhereAsync( + [NotNull] this IEnumerable source, [NotNull] Func> predicate) + { + Check.NotNull(source, "source"); + Check.NotNull(predicate, "predicate"); + + var results = Enumerable.Empty(); + + foreach (var item in source) + { + if (await predicate(item)) + { + results = results.Concat(new[] { item }); + } + } + + return results; + } + } +} diff --git a/src/Microsoft.Data.Core/Utilities/ExpressionExtensions.cs b/src/Microsoft.Data.Core/Utilities/ExpressionExtensions.cs new file mode 100644 index 00000000000..f2d7e061c3a --- /dev/null +++ b/src/Microsoft.Data.Core/Utilities/ExpressionExtensions.cs @@ -0,0 +1,150 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Utilities +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using Microsoft.Data.Core.Resources; + + [DebuggerStepThrough] + internal static class ExpressionExtensions + { + public static PropertyInfo GetPropertyAccess(this LambdaExpression propertyAccessExpression) + { + DebugCheck.NotNull(propertyAccessExpression); + Debug.Assert(propertyAccessExpression.Parameters.Count == 1); + + var propertyInfo + = propertyAccessExpression + .Parameters + .Single() + .MatchSimplePropertyAccess(propertyAccessExpression.Body); + + if (propertyInfo == null) + { + throw new ArgumentException( + Strings.InvalidPropertyExpression(propertyAccessExpression), + "propertyAccessExpression"); + } + + return propertyInfo; + } + + public static IList GetPropertyAccessList(this LambdaExpression propertyAccessExpression) + { + DebugCheck.NotNull(propertyAccessExpression); + Debug.Assert(propertyAccessExpression.Parameters.Count == 1); + + var propertyPaths + = MatchPropertyAccessList(propertyAccessExpression, (p, e) => e.MatchSimplePropertyAccess(p)); + + if (propertyPaths == null) + { + throw new ArgumentException( + Strings.InvalidPropertiesExpression(propertyAccessExpression), + "propertyAccessExpression"); + } + + return propertyPaths; + } + + private static IList MatchPropertyAccessList( + this LambdaExpression lambdaExpression, Func propertyMatcher) + { + DebugCheck.NotNull(lambdaExpression); + DebugCheck.NotNull(propertyMatcher); + Debug.Assert(lambdaExpression.Body != null); + + var newExpression + = RemoveConvert(lambdaExpression.Body) as NewExpression; + + if (newExpression != null) + { + var parameterExpression + = lambdaExpression.Parameters.Single(); + + var propertyInfos + = newExpression + .Arguments + .Select(a => propertyMatcher(a, parameterExpression)) + .Where(p => p != null) + .ToList(); + + if (propertyInfos.Count != newExpression.Arguments.Count) + { + return null; + } + + return propertyInfos; + } + + var propertyPath + = propertyMatcher(lambdaExpression.Body, lambdaExpression.Parameters.Single()); + + return (propertyPath != null) ? new[] { propertyPath } : null; + } + + private static PropertyInfo MatchSimplePropertyAccess( + this Expression parameterExpression, Expression propertyAccessExpression) + { + DebugCheck.NotNull(propertyAccessExpression); + + var propertyInfos = MatchPropertyAccess(parameterExpression, propertyAccessExpression); + + return propertyInfos != null && propertyInfos.Length == 1 ? propertyInfos[0] : null; + } + + private static PropertyInfo[] MatchPropertyAccess( + this Expression parameterExpression, Expression propertyAccessExpression) + { + DebugCheck.NotNull(parameterExpression); + DebugCheck.NotNull(propertyAccessExpression); + + var propertyInfos = new List(); + + MemberExpression memberExpression; + + do + { + memberExpression = RemoveConvert(propertyAccessExpression) as MemberExpression; + + if (memberExpression == null) + { + return null; + } + + var propertyInfo = memberExpression.Member as PropertyInfo; + + if (propertyInfo == null) + { + return null; + } + + propertyInfos.Insert(0, propertyInfo); + + propertyAccessExpression = memberExpression.Expression; + } + while (memberExpression.Expression != parameterExpression); + + return propertyInfos.ToArray(); + } + + private static Expression RemoveConvert(this Expression expression) + { + DebugCheck.NotNull(expression); + + while ((expression != null) + && (expression.NodeType == ExpressionType.Convert + || expression.NodeType == ExpressionType.ConvertChecked)) + { + expression = RemoveConvert(((UnaryExpression)expression).Operand); + } + + return expression; + } + } +} diff --git a/src/Microsoft.Data.Core/Utilities/LazyRef.cs b/src/Microsoft.Data.Core/Utilities/LazyRef.cs new file mode 100644 index 00000000000..34463041d05 --- /dev/null +++ b/src/Microsoft.Data.Core/Utilities/LazyRef.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Utilities +{ + using System; + using System.Diagnostics; + using System.Threading; + using JetBrains.Annotations; + + [DebuggerStepThrough] + public sealed class LazyRef + where T : class + { + private Func _initializer; + private object _syncLock; + + private T _value; + + internal LazyRef() + { + } + + public LazyRef([NotNull] Func initializer) + { + Check.NotNull(initializer, "initializer"); + + _initializer = initializer; + } + + public T Value + { + get + { + if (_value == null) + { + var syncLock = new object(); + + syncLock + = Interlocked.CompareExchange(ref _syncLock, syncLock, null) + ?? syncLock; + + lock (syncLock) + { + if (_value == null) + { + _value = _initializer(); + + _syncLock = null; + _initializer = null; + } + } + } + + return _value; + } + } + + public void ExchangeValue([NotNull] Func newValueCreator) + { + Check.NotNull(newValueCreator, "newValueCreator"); + + T originalValue, newValue; + + do + { + originalValue = Value; + newValue = newValueCreator(originalValue); + + if (ReferenceEquals(newValue, originalValue)) + { + return; + } + } + while (Interlocked.CompareExchange(ref _value, newValue, originalValue) != originalValue); + } + + public bool HasValue + { + get { return _value != null; } + } + } +} diff --git a/src/Microsoft.Data.Core/Utilities/StringExtensions.cs b/src/Microsoft.Data.Core/Utilities/StringExtensions.cs new file mode 100644 index 00000000000..2b6ae776af4 --- /dev/null +++ b/src/Microsoft.Data.Core/Utilities/StringExtensions.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Utilities +{ + using System; + using System.Diagnostics; + using System.Globalization; + + [DebuggerStepThrough] + public static class StringExtensions + { + public static bool EqualsOrdinal(this string s1, string s2) + { + return string.Equals(s1, s2, StringComparison.Ordinal); + } + + public static bool EqualsIgnoreCase(this string s1, string s2) + { + return string.Equals(s1, s2, StringComparison.OrdinalIgnoreCase); + } + + public static string Format(this string s, params object[] args) + { + return string.Format(CultureInfo.InvariantCulture, s, args); + } + } +} diff --git a/src/Microsoft.Data.Core/Utilities/TypeExtensions.cs b/src/Microsoft.Data.Core/Utilities/TypeExtensions.cs new file mode 100644 index 00000000000..5d1774f50b3 --- /dev/null +++ b/src/Microsoft.Data.Core/Utilities/TypeExtensions.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Utilities +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using JetBrains.Annotations; + + [DebuggerStepThrough] + public static class TypeExtensions + { + public static Type ElementType([NotNull] this Type type) + { + Check.NotNull(type, "type"); + + var typeInfo = type.GetTypeInfo(); + + if (typeInfo.IsGenericType + && (type.GetGenericTypeDefinition() == typeof(IQueryable<>) + || type.GetGenericTypeDefinition() == typeof(IEnumerable<>))) + { + return typeInfo.GenericTypeArguments.Single(); + } + + return type; + } + } +} diff --git a/src/Microsoft.Data.Core/packages.config b/src/Microsoft.Data.Core/packages.config new file mode 100644 index 00000000000..1dee698e5c6 --- /dev/null +++ b/src/Microsoft.Data.Core/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/Microsoft.Data.Core/project.json b/src/Microsoft.Data.Core/project.json new file mode 100644 index 00000000000..6d890e55b4e --- /dev/null +++ b/src/Microsoft.Data.Core/project.json @@ -0,0 +1,11 @@ +{ + "version" : "0.1-pre-*", + "dependencies": { + "Microsoft.AspNet.DependencyInjection" : "0.1-alpha-*", + "Microsoft.Bcl.Immutable" : "1.0.30" + }, + "configurations": { + "net45": {}, + "k10": {} + } +} \ No newline at end of file diff --git a/src/Microsoft.Data.InMemory/Microsoft.Data.InMemory.csproj b/src/Microsoft.Data.InMemory/Microsoft.Data.InMemory.csproj new file mode 100644 index 00000000000..0e4401f43ec --- /dev/null +++ b/src/Microsoft.Data.InMemory/Microsoft.Data.InMemory.csproj @@ -0,0 +1,54 @@ + + + + + 12.0 + Debug + AnyCPU + {01F860AB-80DD-4AB4-B495-D5B20A493E60} + Library + Properties + Microsoft.Data.InMemory + Microsoft.Data.InMemory + v4.6 + Profile44 + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + true + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.Data.InMemory/project.json b/src/Microsoft.Data.InMemory/project.json new file mode 100644 index 00000000000..6f2dfc0aa2f --- /dev/null +++ b/src/Microsoft.Data.InMemory/project.json @@ -0,0 +1,8 @@ +{ + "version" : "0.1-pre-*", + "dependencies": {}, + "configurations": { + "net45": {}, + "k10": {} + } +} \ No newline at end of file diff --git a/src/Microsoft.Data.Relational/Microsoft.Data.Relational.csproj b/src/Microsoft.Data.Relational/Microsoft.Data.Relational.csproj new file mode 100644 index 00000000000..900549f7b3c --- /dev/null +++ b/src/Microsoft.Data.Relational/Microsoft.Data.Relational.csproj @@ -0,0 +1,54 @@ + + + + + 12.0 + Debug + AnyCPU + {8C045113-8E76-4910-9C61-95BC82BB3003} + Library + Properties + Microsoft.Data.Relational + Microsoft.Data.Relational + v4.6 + Profile44 + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + true + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.Data.Relational/project.json b/src/Microsoft.Data.Relational/project.json new file mode 100644 index 00000000000..6f2dfc0aa2f --- /dev/null +++ b/src/Microsoft.Data.Relational/project.json @@ -0,0 +1,8 @@ +{ + "version" : "0.1-pre-*", + "dependencies": {}, + "configurations": { + "net45": {}, + "k10": {} + } +} \ No newline at end of file diff --git a/src/Microsoft.Data.SQLite/Microsoft.Data.SQLite.csproj b/src/Microsoft.Data.SQLite/Microsoft.Data.SQLite.csproj new file mode 100644 index 00000000000..d0dc865b8fe --- /dev/null +++ b/src/Microsoft.Data.SQLite/Microsoft.Data.SQLite.csproj @@ -0,0 +1,54 @@ + + + + + 12.0 + Debug + AnyCPU + {33D71368-9175-4A74-831E-11ACE6D815EE} + Library + Properties + Microsoft.Data.SQLite + Microsoft.Data.SQLite + v4.6 + Profile44 + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + true + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.Data.SQLite/project.json b/src/Microsoft.Data.SQLite/project.json new file mode 100644 index 00000000000..6f2dfc0aa2f --- /dev/null +++ b/src/Microsoft.Data.SQLite/project.json @@ -0,0 +1,8 @@ +{ + "version" : "0.1-pre-*", + "dependencies": {}, + "configurations": { + "net45": {}, + "k10": {} + } +} \ No newline at end of file diff --git a/src/Microsoft.Data.SqlServer/Microsoft.Data.SqlServer.csproj b/src/Microsoft.Data.SqlServer/Microsoft.Data.SqlServer.csproj new file mode 100644 index 00000000000..b476569fe62 --- /dev/null +++ b/src/Microsoft.Data.SqlServer/Microsoft.Data.SqlServer.csproj @@ -0,0 +1,54 @@ + + + + + 12.0 + Debug + AnyCPU + {D30CDA1E-CD18-42CE-934F-F33162F1B6EC} + Library + Properties + Microsoft.Data.SqlServer + Microsoft.Data.SqlServer + v4.6 + Profile44 + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + true + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlServer/project.json b/src/Microsoft.Data.SqlServer/project.json new file mode 100644 index 00000000000..6f2dfc0aa2f --- /dev/null +++ b/src/Microsoft.Data.SqlServer/project.json @@ -0,0 +1,8 @@ +{ + "version" : "0.1-pre-*", + "dependencies": {}, + "configurations": { + "net45": {}, + "k10": {} + } +} \ No newline at end of file diff --git a/test/Microsoft.Data.Tests.Functional/ApiStyle.cs b/test/Microsoft.Data.Tests.Functional/ApiStyle.cs new file mode 100644 index 00000000000..948ab4c1429 --- /dev/null +++ b/test/Microsoft.Data.Tests.Functional/ApiStyle.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using JetBrains.Annotations; + using Microsoft.Data.Core.Metadata; + using Xunit; + + public class ApiStyle + { + private const BindingFlags PublicInstance + = BindingFlags.Instance | BindingFlags.Public; + + [Fact] + public void Public_inheritable_apis_should_be_virtual() + { + var assembly = typeof(Entity).Assembly; + + var nonVirtualMethods + = from t in GetAllTypes(assembly.GetTypes()) + where t.IsVisible + && !t.IsSealed + && t.GetConstructors(PublicInstance).Any() + from m in t.GetMethods(PublicInstance) + where m.DeclaringType != null + && m.DeclaringType.Assembly == assembly + && !m.IsVirtual + select t.Name + "." + m.Name; + + Assert.Equal("", string.Join("\r\n", nonVirtualMethods)); + } + + [Fact] + public void Public_api_arguments_should_have_not_null_annotation() + { + var assembly = typeof(Entity).Assembly; + + var parametersMissingAttribute + = from t in GetAllTypes(assembly.GetTypes()) + where t.IsVisible + from m in t.GetMethods(PublicInstance) + where m.DeclaringType != null + && m.DeclaringType.Assembly == assembly + from p in m.GetParameters() + where !p.ParameterType.IsValueType + && p.GetCustomAttribute() == null + select t.Name + "." + m.Name + "[" + p.Name + "]"; + + Assert.Equal("", string.Join("\r\n", parametersMissingAttribute)); + } + + [Fact] + public void Fluent_api_methods_should_not_return_void() + { + var fluentApiTypes = new[] { typeof(ModelBuilder) }; + + var assembly = typeof(Entity).Assembly; + + var voidMethods + = from t in GetAllTypes(fluentApiTypes) + where t.IsVisible + from m in t.GetMethods(PublicInstance) + where m.DeclaringType != null + && m.DeclaringType.Assembly == assembly + && m.ReturnType == typeof(void) + select t.Name + "." + m.Name; + + Assert.Equal("", string.Join("\r\n", voidMethods)); + } + + private static IEnumerable GetAllTypes(IEnumerable types) + { + foreach (var type in types) + { + yield return type; + + foreach (var nestedType in GetAllTypes(type.GetNestedTypes())) + { + yield return nestedType; + } + } + } + } +} diff --git a/test/Microsoft.Data.Tests.Functional/Microsoft.Data.Core.FunctionalTest.csproj b/test/Microsoft.Data.Tests.Functional/Microsoft.Data.Core.FunctionalTest.csproj new file mode 100644 index 00000000000..3526f1ea550 --- /dev/null +++ b/test/Microsoft.Data.Tests.Functional/Microsoft.Data.Core.FunctionalTest.csproj @@ -0,0 +1,67 @@ + + + + + Debug + AnyCPU + {A08C9073-CB43-409E-9A5E-4BE2CA687FE3} + Library + Properties + Microsoft.Data.Core + Microsoft.Data.Core.FunctionalTest + v4.5.1 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + ..\..\packages\xunit.1.9.2\lib\net20\xunit.dll + + + + + + + + + + + + + + + + + + {6a531bb3-df57-4743-af3b-4be431b67de6} + Microsoft.Data.Core + + + + + \ No newline at end of file diff --git a/test/Microsoft.Data.Tests.Functional/packages.config b/test/Microsoft.Data.Tests.Functional/packages.config new file mode 100644 index 00000000000..5b00e3b3354 --- /dev/null +++ b/test/Microsoft.Data.Tests.Functional/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/test/Microsoft.Data.Tests.Functional/project.json b/test/Microsoft.Data.Tests.Functional/project.json new file mode 100644 index 00000000000..6f2dfc0aa2f --- /dev/null +++ b/test/Microsoft.Data.Tests.Functional/project.json @@ -0,0 +1,8 @@ +{ + "version" : "0.1-pre-*", + "dependencies": {}, + "configurations": { + "net45": {}, + "k10": {} + } +} \ No newline at end of file diff --git a/test/Microsoft.Data.Tests.Unit/Metadata/AnnotationFacts.cs b/test/Microsoft.Data.Tests.Unit/Metadata/AnnotationFacts.cs new file mode 100644 index 00000000000..7a5a53045da --- /dev/null +++ b/test/Microsoft.Data.Tests.Unit/Metadata/AnnotationFacts.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Metadata +{ + using Xunit; + + public class AnnotationFacts + { + [Fact] + public void Can_create_annotation() + { + var annotation = new Annotation("Foo", "Bar"); + + Assert.Equal("Foo", annotation.Name); + Assert.Equal("Bar", annotation.Value); + } + } +} diff --git a/test/Microsoft.Data.Tests.Unit/Metadata/EntityFacts.cs b/test/Microsoft.Data.Tests.Unit/Metadata/EntityFacts.cs new file mode 100644 index 00000000000..6c4230bea33 --- /dev/null +++ b/test/Microsoft.Data.Tests.Unit/Metadata/EntityFacts.cs @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Metadata +{ + using System.Linq; + using System.Reflection; + using Xunit; + + public class EntityFacts + { + #region Fixture + + public class Customer + { + public static PropertyInfo IdProperty = typeof(Customer).GetProperty("Id"); + public static PropertyInfo NameProperty = typeof(Customer).GetProperty("Name"); + + public int Id { get; set; } + public string Name { get; set; } + } + + #endregion + + [Fact] + public void Can_create_entity() + { + var entity = new Entity(typeof(Customer)); + + Assert.Equal("Customer", entity.Name); + Assert.Same(typeof(Customer), entity.Type); + } + + [Fact] + public void Can_add_and_remove_properties() + { + var entity = new Entity(typeof(Customer)); + + var property1 = new Property(Customer.IdProperty); + var property2 = new Property(Customer.NameProperty); + + entity.AddProperty(property1); + entity.AddProperty(property2); + + Assert.True(new[] { property1, property2 }.SequenceEqual(entity.Properties)); + + entity.RemoveProperty(property1); + + Assert.True(new[] { property2 }.SequenceEqual(entity.Properties)); + } + + [Fact] + public void Properties_are_ordered_by_name() + { + var entity = new Entity(typeof(Customer)); + + var property1 = new Property(Customer.IdProperty); + var property2 = new Property(Customer.NameProperty); + + entity.AddProperty(property2); + entity.AddProperty(property1); + + Assert.True(new[] { property1, property2 }.SequenceEqual(entity.Properties)); + } + + [Fact] + public void Can_set_and_reset_key() + { + var entity = new Entity(typeof(Customer)); + + var property1 = new Property(Customer.IdProperty); + var property2 = new Property(Customer.NameProperty); + + entity.Key = new[] { property1, property2 }; + + Assert.True(new[] { property1, property2 }.SequenceEqual(entity.Key)); + Assert.True(new[] { property1, property2 }.SequenceEqual(entity.Properties)); + + entity.RemoveProperty(property1); + + Assert.True(new[] { property2 }.SequenceEqual(entity.Key)); + Assert.True(new[] { property2 }.SequenceEqual(entity.Properties)); + + entity.Key = new[] { property1 }; + + Assert.True(new[] { property1 }.SequenceEqual(entity.Key)); + Assert.True(new[] { property1, property2 }.SequenceEqual(entity.Properties)); + } + + [Fact] + public void Setting_key_properties_should_update_existing_properties() + { + var entity = new Entity(typeof(Customer)); + + entity.AddProperty(new Property(Customer.IdProperty)); + + var newIdProperty = new Property(Customer.IdProperty); + + var property2 = new Property(Customer.NameProperty); + + entity.Key = new[] { newIdProperty, property2 }; + + Assert.True(new[] { newIdProperty, property2 }.SequenceEqual(entity.Properties)); + } + + [Fact] + public void Can_clear_key() + { + var entity = new Entity(typeof(Customer)); + + var property1 = new Property(Customer.IdProperty); + var property2 = new Property(Customer.NameProperty); + + entity.Key = new[] { property1, property2 }; + + Assert.Equal(2, entity.Key.Count()); + + entity.Key = new Property[] { }; + + Assert.Equal(0, entity.Key.Count()); + } + } +} diff --git a/test/Microsoft.Data.Tests.Unit/Metadata/MetadataBaseFacts.cs b/test/Microsoft.Data.Tests.Unit/Metadata/MetadataBaseFacts.cs new file mode 100644 index 00000000000..bc129f7f0ac --- /dev/null +++ b/test/Microsoft.Data.Tests.Unit/Metadata/MetadataBaseFacts.cs @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Metadata +{ + using System.Linq; + using Xunit; + + public class MetadataBaseFacts + { + #region Fixture + + private class ConcreteMetadata : MetadataBase + { + public ConcreteMetadata() + : base("Test") + { + } + + public ConcreteMetadata(string name) + : base(name) + { + } + } + + #endregion + + [Fact] + public void Can_set_name_via_ctor() + { + var metadataBase = new ConcreteMetadata("Foo"); + + Assert.Equal("Foo", metadataBase.Name); + } + + [Fact] + public void StorageName_defaults_to_name() + { + var metadataBase = new ConcreteMetadata("Foo"); + + Assert.Equal("Foo", metadataBase.StorageName); + } + + [Fact] + public void StorageName_can_be_different_from_name() + { + var metadataBase = new ConcreteMetadata("Foo") { StorageName = "Bar" }; + + Assert.Equal("Foo", metadataBase.Name); + Assert.Equal("Bar", metadataBase.StorageName); + } + + [Fact] + public void Can_add_annotation() + { + var metadataBase = new ConcreteMetadata(); + + metadataBase.AddAnnotation(new Annotation("Foo", "Bar")); + + Assert.Equal("Bar", metadataBase["Foo"]); + } + + [Fact] + public void Can_remove_annotation() + { + var metadataBase = new ConcreteMetadata(); + var annotation = new Annotation("Foo", "Bar"); + + metadataBase.AddAnnotation(annotation); + + Assert.Equal("Bar", metadataBase["Foo"]); + + metadataBase.RemoveAnnotation(annotation); + + Assert.Null(metadataBase["Foo"]); + } + + [Fact] + public void Can_update_existing_annotation() + { + var metadataBase = new ConcreteMetadata(); + var annotation = new Annotation("Foo", "Bar"); + + metadataBase.AddAnnotation(annotation); + + Assert.Equal("Bar", metadataBase["Foo"]); + + metadataBase["Foo"] = "Baz"; + + Assert.Equal("Baz", metadataBase["Foo"]); + } + + [Fact] + public void Can_get_set_model_annotations_via_indexer() + { + var metadataBase = new ConcreteMetadata(); + + metadataBase["foo"] = "bar"; + + Assert.Equal("bar", metadataBase["foo"]); + } + + [Fact] + public void Annotations_are_ordered_by_name() + { + var metadataBase = new ConcreteMetadata(); + + var annotation1 = new Annotation("Z", "Foo"); + var annotation2 = new Annotation("A", "Bar"); + + metadataBase.AddAnnotation(annotation1); + metadataBase.AddAnnotation(annotation2); + + Assert.True(new[] { annotation2, annotation1 }.SequenceEqual(metadataBase.Annotations)); + } + } +} diff --git a/test/Microsoft.Data.Tests.Unit/Metadata/ModelBuilderFacts.cs b/test/Microsoft.Data.Tests.Unit/Metadata/ModelBuilderFacts.cs new file mode 100644 index 00000000000..413f0b2695c --- /dev/null +++ b/test/Microsoft.Data.Tests.Unit/Metadata/ModelBuilderFacts.cs @@ -0,0 +1,113 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Metadata +{ + using System.Linq; + using System.Reflection; + using Xunit; + + public class ModelBuilderFacts + { + #region Fixture + + public class Customer + { + public static PropertyInfo NameProperty + = typeof(Customer).GetProperty("Name"); + + public int Id { get; set; } + public string Name { get; set; } + } + + #endregion + + [Fact] + public void Can_get_entity_builder_for_clr_type() + { + var model = new Model(); + var modelBuilder = new ModelBuilder(model); + + var entityBuilder = modelBuilder.Entity(); + + Assert.NotNull(entityBuilder); + Assert.Equal("Customer", model.Entity(typeof(Customer)).Name); + } + + [Fact] + public void Can_set_entity_key_from_clr_properties() + { + var model = new Model(); + var modelBuilder = new ModelBuilder(model); + + modelBuilder.Entity().Key(e => e.Id); + + var entity = model.Entity(typeof(Customer)); + + Assert.Equal(1, entity.Key.Count()); + Assert.Equal("Id", entity.Key.First().Name); + } + + [Fact] + public void Can_set_composite_entity_key_from_clr_properties() + { + var model = new Model(); + var modelBuilder = new ModelBuilder(model); + + modelBuilder + .Entity() + .Key(e => new { e.Id, e.Name }); + + var entity = model.Entity(typeof(Customer)); + + Assert.Equal(2, entity.Key.Count()); + Assert.Equal("Id", entity.Key.First().Name); + Assert.Equal("Name", entity.Key.Last().Name); + } + + [Fact] + public void Can_set_entity_annotation() + { + var model = new Model(); + var modelBuilder = new ModelBuilder(model); + + modelBuilder + .Entity() + .Annotation("foo", "bar"); + + Assert.Equal("bar", model.Entity(typeof(Customer))["foo"]); + } + + [Fact] + public void Can_set_property_annotation() + { + var model = new Model(); + var modelBuilder = new ModelBuilder(model); + + modelBuilder + .Entity() + .Properties(ps => ps.Property(c => c.Name).Annotation("foo", "bar")); + + Assert.Equal( + "bar", + model.Entity(typeof(Customer)).Property(Customer.NameProperty)["foo"]); + } + + [Fact] + public void Can_add_multiple_properties() + { + var model = new Model(); + var modelBuilder = new ModelBuilder(model); + + modelBuilder + .Entity() + .Properties( + ps => + { + ps.Property(c => c.Id); + ps.Property(c => c.Name).Annotation("foo", "bar"); + }); + + Assert.Equal(2, model.Entity(typeof(Customer)).Properties.Count()); + } + } +} diff --git a/test/Microsoft.Data.Tests.Unit/Metadata/ModelFacts.cs b/test/Microsoft.Data.Tests.Unit/Metadata/ModelFacts.cs new file mode 100644 index 00000000000..8bba9956177 --- /dev/null +++ b/test/Microsoft.Data.Tests.Unit/Metadata/ModelFacts.cs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Metadata +{ + using System.Linq; + using Xunit; + + public class ModelFacts + { + #region Fixture + + public class Customer + { + public int Id { get; set; } + public string Name { get; set; } + } + + public class Order + { + } + + #endregion + + [Fact] + public void Can_add_and_remove_entity() + { + var model = new Model(); + var entity = new Entity(typeof(Customer)); + + model.AddEntity(entity); + + Assert.NotNull(model.Entity(new Customer())); + + model.RemoveEntity(entity); + + Assert.Null(model.Entity(new Customer())); + } + + [Fact] + public void Can_get_entity_by_instance() + { + var model = new Model(); + model.AddEntity(new Entity(typeof(Customer))); + + var entity = model.Entity(new Customer()); + + Assert.NotNull(entity); + Assert.Equal("Customer", entity.Name); + Assert.Same(entity, model.Entity(typeof(Customer))); + } + + [Fact] + public void Can_get_entity_by_type() + { + var model = new Model(); + model.AddEntity(new Entity(typeof(Customer))); + + var entity = model.Entity(typeof(Customer)); + + Assert.NotNull(entity); + Assert.Equal("Customer", entity.Name); + Assert.Same(entity, model.Entity(typeof(Customer))); + } + + [Fact] + public void Entities_are_ordered_by_name() + { + var model = new Model(); + var entity1 = new Entity(typeof(Order)); + var entity2 = new Entity(typeof(Customer)); + + model.AddEntity(entity1); + model.AddEntity(entity2); + + Assert.True(new[] { entity2, entity1 }.SequenceEqual(model.Entities)); + } + } +} diff --git a/test/Microsoft.Data.Tests.Unit/Metadata/PropertyFacts.cs b/test/Microsoft.Data.Tests.Unit/Metadata/PropertyFacts.cs new file mode 100644 index 00000000000..5cf39553ac2 --- /dev/null +++ b/test/Microsoft.Data.Tests.Unit/Metadata/PropertyFacts.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Metadata +{ + using System.Reflection; + using Xunit; + + public class PropertyFacts + { + #region Fixture + + public class Customer + { + public static PropertyInfo NameProperty = typeof(Customer).GetProperty("Name"); + + public string Name { get; set; } + } + + #endregion + + [Fact] + public void Can_create_property() + { + var property = new Property(Customer.NameProperty); + + Assert.Equal("Name", property.Name); + Assert.Same(Customer.NameProperty, property.PropertyInfo); + } + } +} diff --git a/test/Microsoft.Data.Tests.Unit/Microsoft.Data.Core.UnitTest.csproj b/test/Microsoft.Data.Tests.Unit/Microsoft.Data.Core.UnitTest.csproj new file mode 100644 index 00000000000..e8f0f7ca98c --- /dev/null +++ b/test/Microsoft.Data.Tests.Unit/Microsoft.Data.Core.UnitTest.csproj @@ -0,0 +1,83 @@ + + + + + Debug + AnyCPU + {FB272D58-D5FD-41D1-9E02-BCE17FD97F0E} + Library + Properties + Microsoft.Data.Core + Microsoft.Data.Core.UnitTest + v4.5.1 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + ..\..\packages\xunit.1.9.2\lib\net20\xunit.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {6a531bb3-df57-4743-af3b-4be431b67de6} + Microsoft.Data.Core + + + {01f860ab-80dd-4ab4-b495-d5b20a493e60} + Microsoft.Data.InMemory + + + + + \ No newline at end of file diff --git a/test/Microsoft.Data.Tests.Unit/Utilities/CheckFacts.cs b/test/Microsoft.Data.Tests.Unit/Utilities/CheckFacts.cs new file mode 100644 index 00000000000..3df754878df --- /dev/null +++ b/test/Microsoft.Data.Tests.Unit/Utilities/CheckFacts.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Utilities +{ + using System; + using Xunit; + + public class CheckFacts + { + [Fact] + public void NotNull_throws_when_arg_is_null() + { +// ReSharper disable once NotResolvedInText + Assert.Throws(() => Check.NotNull(null, "foo")); + } + + [Fact] + public void NotNull_throws_when_arg_name_empty() + { + Assert.Throws(() => Check.NotNull(new object(), string.Empty)); + } + + [Fact] + public void NotEmpty_throws_when_empty() + { + Assert.Throws(() => Check.NotEmpty("", string.Empty)); + } + + [Fact] + public void NotEmpty_throws_when_whitespace() + { + Assert.Throws(() => Check.NotEmpty(" ", string.Empty)); + } + + [Fact] + public void NotEmpty_throws_when_parameter_name_null() + { + Assert.Throws(() => Check.NotEmpty("42", string.Empty)); + } + } +} diff --git a/test/Microsoft.Data.Tests.Unit/Utilities/DebugCheckFacts.cs b/test/Microsoft.Data.Tests.Unit/Utilities/DebugCheckFacts.cs new file mode 100644 index 00000000000..93e6c7c588f --- /dev/null +++ b/test/Microsoft.Data.Tests.Unit/Utilities/DebugCheckFacts.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Utilities +{ + using Xunit; + using Xunit.Sdk; + + public class DebugCheckFacts + { + [Fact] + public void NotNull_throws_when_arg_is_null() + { + Assert.Throws(() => DebugCheck.NotNull(null)); + } + + [Fact] + public void NotEmpty_throws_when_empty() + { + Assert.Throws(() => DebugCheck.NotEmpty("")); + } + + [Fact] + public void NotEmpty_throws_when_whitespace() + { + Assert.Throws(() => DebugCheck.NotEmpty(" ")); + } + } +} diff --git a/test/Microsoft.Data.Tests.Unit/Utilities/EnumerableExtensionsFacts.cs b/test/Microsoft.Data.Tests.Unit/Utilities/EnumerableExtensionsFacts.cs new file mode 100644 index 00000000000..6680877ab3b --- /dev/null +++ b/test/Microsoft.Data.Tests.Unit/Utilities/EnumerableExtensionsFacts.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Utilities +{ + using System.Linq; + using System.Threading.Tasks; + using Xunit; + + public class EnumerableExtensionsFacts + { + [Fact] + public void OrderByOrdinal_should_respect_case() + { + Assert.Equal(new[] { "A", "a", "b" }, new[] { "b", "A", "a" }.OrderByOrdinal(s => s)); + } + + [Fact] + public void Join_empty_input_returns_empty_string() + { + Assert.Equal("", new object[] { }.Join()); + } + + [Fact] + public void Join_single_element_does_not_use_separator() + { + Assert.Equal("42", new object[] { 42 }.Join()); + } + + [Fact] + public void Join_should_use_comma_by_default() + { + Assert.Equal("42, bar", new object[] { 42, "bar" }.Join()); + } + + [Fact] + public void Join_should_use_explicit_separator_when_provided() + { + Assert.Equal("42-bar", new object[] { 42, "bar" }.Join("-")); + } + + [Fact] + public async Task SelectAsync_should_apply_selector_over_sequence() + { + Assert.Equal( + new[] { "aa", "bb" }, + await new[] { "AA", "BB" }.SelectAsync(s => Task.FromResult(s.ToLower()))); + } + + [Fact] + public async Task SelectManyAsync_should_apply_selector_over_inner_sequences() + { + Assert.Equal( + new[] { "a", "b", "c", "d" }, + await new[] { "ab", "cd" } + .SelectManyAsync(s => Task.FromResult(s.Select(c => c.ToString())))); + } + + [Fact] + public async Task WhereAsync_should_apply_filter_over_sequence() + { + Assert.Equal( + new[] { "BB" }, + await new[] { "AA", "BB" }.WhereAsync(s => Task.FromResult(s.StartsWith("B")))); + } + } +} diff --git a/test/Microsoft.Data.Tests.Unit/Utilities/ExpressionExtensionsFacts.cs b/test/Microsoft.Data.Tests.Unit/Utilities/ExpressionExtensionsFacts.cs new file mode 100644 index 00000000000..6435c16a3a7 --- /dev/null +++ b/test/Microsoft.Data.Tests.Unit/Utilities/ExpressionExtensionsFacts.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Utilities +{ + using System; + using System.Linq; + using System.Linq.Expressions; + using Microsoft.Data.Core.Resources; + using Xunit; + + public class ExpressionExtensionsFacts + { + [Fact] + public void GetPropertyAccess_should_return_property_info_when_valid_property_access_expression() + { + Expression> expression = d => d.Hour; + + var propertyInfo = expression.GetPropertyAccess(); + + Assert.NotNull(propertyInfo); + Assert.Equal("Hour", propertyInfo.Name); + } + + [Fact] + public void GetPropertyAccess_should_throw_when_not_property_access() + { + Expression> expression = d => 123; + + Assert.Contains( + Strings.InvalidPropertyExpression(expression), + Assert.Throws(() => expression.GetPropertyAccess()).Message); + } + + [Fact] + public void GetPropertyAccess_should_throw_when_not_property_access_on_the_provided_argument() + { + var closure = DateTime.Now; + Expression> expression = d => closure.Hour; + + Assert.Contains( + Strings.InvalidPropertyExpression(expression), + Assert.Throws(() => expression.GetPropertyAccess()).Message); + } + + [Fact] + public void GetPropertyAccess_should_remove_convert() + { + Expression> expression = d => d.Hour; + + var propertyInfo = expression.GetPropertyAccess(); + + Assert.NotNull(propertyInfo); + Assert.Equal("Hour", propertyInfo.Name); + } + + [Fact] + public void GetPropertyAccessList_should_return_property_info_collection() + { + Expression> expression = d => new + { + d.Date, + d.Day + }; + + var propertyInfos = expression.GetPropertyAccessList(); + + Assert.NotNull(propertyInfos); + Assert.Equal(2, propertyInfos.Count); + Assert.Equal("Date", propertyInfos.First().Name); + Assert.Equal("Day", propertyInfos.Last().Name); + } + + [Fact] + public void GetPropertyAccessList_should_throw_when_invalid_expression() + { + Expression> expression = d => new + { + P = d.AddTicks(23) + }; + + Assert.Contains( + Strings.InvalidPropertiesExpression(expression), + Assert.Throws(() => expression.GetPropertyAccessList()).Message); + } + + [Fact] + public void GetPropertyAccessList_should_throw_when_property_access_not_on_the_provided_argument() + { + var closure = DateTime.Now; + + Expression> expression = d => new + { + d.Date, + closure.Day + }; + + Assert.Contains( + Strings.InvalidPropertiesExpression(expression), + Assert.Throws(() => expression.GetPropertyAccessList()).Message); + } + } +} diff --git a/test/Microsoft.Data.Tests.Unit/Utilities/LazyRefFacts.cs b/test/Microsoft.Data.Tests.Unit/Utilities/LazyRefFacts.cs new file mode 100644 index 00000000000..2b4a3b47b87 --- /dev/null +++ b/test/Microsoft.Data.Tests.Unit/Utilities/LazyRefFacts.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Utilities +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using Xunit; + + public class LazyRefFacts + { + [Fact] + public async Task Can_initialize_from_multiple_threads_and_initialization_happens_only_once() + { + var counter = 0; + var safeLazy = new LazyRef(() => counter++.ToString()); + var tasks = new List(); + + for (var i = 0; i < 10; i++) + { + tasks.Add(Task.Run(() => safeLazy.Value)); + } + + await Task.WhenAll(tasks); + + Assert.Equal(1, counter); + } + + [Fact] + public async Task Can_exchange_value() + { + var safeLazy = new LazyRef(() => ""); + var tasks = new List(); + + for (var i = 0; i < 10; i++) + { + tasks.Add(Task.Run(() => safeLazy.ExchangeValue(s => s + "s"))); + } + + await Task.WhenAll(tasks); + + Assert.Equal("ssssssssss", safeLazy.Value); + } + + [Fact] + public void Has_value_is_false_until_value_accessed() + { + var safeLazy = new LazyRef(() => "s"); + + Assert.False(safeLazy.HasValue); + Assert.Equal("s", safeLazy.Value); + Assert.True(safeLazy.HasValue); + } + } +} diff --git a/test/Microsoft.Data.Tests.Unit/Utilities/StringExtensionsFacts.cs b/test/Microsoft.Data.Tests.Unit/Utilities/StringExtensionsFacts.cs new file mode 100644 index 00000000000..9dce927aa8a --- /dev/null +++ b/test/Microsoft.Data.Tests.Unit/Utilities/StringExtensionsFacts.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Utilities +{ + using Xunit; + + public class StringExtensionsFacts + { + [Fact] + public void EqualsOrdinal_should_consider_case() + { + Assert.True("Ab".EqualsOrdinal("Ab")); + Assert.False("Ab".EqualsOrdinal("ab")); + } + + [Fact] + public void EqualsIgnoreCase_should_not_consider_case() + { + Assert.True("Ab".EqualsIgnoreCase("Ab")); + Assert.True("Ab".EqualsIgnoreCase("ab")); + } + + [Fact] + public void Format_should_embed_parameters() + { + Assert.Equal("A1b", "A{0}b".Format(1)); + } + } +} diff --git a/test/Microsoft.Data.Tests.Unit/Utilities/TypeExtensionsFacts.cs b/test/Microsoft.Data.Tests.Unit/Utilities/TypeExtensionsFacts.cs new file mode 100644 index 00000000000..78a822f2158 --- /dev/null +++ b/test/Microsoft.Data.Tests.Unit/Utilities/TypeExtensionsFacts.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.Data.Core.Utilities +{ + using System.Collections.Generic; + using System.Linq; + using Xunit; + + public class TypeExtensionsFacts + { + [Fact] + public void ElementType_should_return_element_type_from_sequence_type() + { + Assert.Equal(typeof(string), typeof(IEnumerable).ElementType()); + Assert.Equal(typeof(string), typeof(IQueryable).ElementType()); + } + + [Fact] + public void ElementType_should_return_input_type_when_not_sequence_type() + { + Assert.Equal(typeof(string), typeof(string)); + } + } +} diff --git a/test/Microsoft.Data.Tests.Unit/packages.config b/test/Microsoft.Data.Tests.Unit/packages.config new file mode 100644 index 00000000000..5b00e3b3354 --- /dev/null +++ b/test/Microsoft.Data.Tests.Unit/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/test/Microsoft.Data.Tests.Unit/project.json b/test/Microsoft.Data.Tests.Unit/project.json new file mode 100644 index 00000000000..6f2dfc0aa2f --- /dev/null +++ b/test/Microsoft.Data.Tests.Unit/project.json @@ -0,0 +1,8 @@ +{ + "version" : "0.1-pre-*", + "dependencies": {}, + "configurations": { + "net45": {}, + "k10": {} + } +} \ No newline at end of file