diff --git a/README.md b/README.md index bce027090f..2758ad3edb 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,15 @@ Umbraco CMS =========== -Umbraco is a free open source Content Management System built on the ASP.NET platform. +The friendliest, most flexible and fastest growing ASP.NET CMS used by more than 350,000 websites worldwide: [https://umbraco.com](https://umbraco.com) + +[![ScreenShot](vimeo.png)](https://vimeo.com/172382998/) + +## Umbraco CMS ## +Umbraco is a free open source Content Management System built on the ASP.NET platform. Our mission is to help you deliver delightful digital experiences by making Umbraco friendly, simpler and social. + ## Building Umbraco from source ## + The easiest way to get started is to run `build/build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `grunt vs` in `src\Umbraco.Web.UI.Client`. If you're interested in making changes to Belle without running Visual Studio make sure to read the [Belle ReadMe file](src/Umbraco.Web.UI.Client/README.md). @@ -13,30 +20,33 @@ Note that you can always [download a nightly build](http://nightly.umbraco.org/? [![ScreenShot](http://umbraco.com/images/whatisumbraco.png)](https://umbraco.tv/videos/umbraco-v7/content-editor/basics/introduction/cms-explanation/) -## Umbraco - the simple, flexible and friendly ASP.NET CMS ## +## Umbraco - The Friendly CMS ## -**More than 350,000 sites trust Umbraco** +For the first time on the Microsoft platform, there is a free user and developer friendly CMS that makes it quick and easy to create websites - or a breeze to build complex web applications. Umbraco has award-winning integration capabilities and supports ASP.NET MVC or Web Forms, including User and Custom Controls, out of the box. -For the first time on the Microsoft platform, there is a free user and developer friendly CMS that makes it quick and easy to create websites - or a breeze to build complex web applications. Umbraco has award-winning integration capabilities and supports ASP.NET MVC or Web Forms, including User and Custom Controls, out of the box. It's a developer's dream and your users will love it too. +Umbraco is not only loved by developers, but is a content editors dream. Enjoy intuitive editing tools, media management, responsive views and approval workflows to send your content live. -Used by more than 350,000 active websites including [http://daviscup.com](http://daviscup.com), [http://heinz.com](http://heinz.com), [http://peugeot.com](http://peugeot.com), [http://www.hersheys.com/](http://www.hersheys.com/) and **The Official ASP.NET and IIS.NET website from Microsoft** ([http://asp.net](http://asp.net) / [http://iis.net](http://iis.net)), you can be sure that the technology is proven, stable and scales. +Used by more than 350,000 active websites including Carlsberg, Segway, Amazon and Heinz and **The Official ASP.NET and IIS.NET website from Microsoft** ([https://asp.net](https://asp.net) / [https://iis.net](https://iis.net)), you can be sure that the technology is proven, stable and scales. Backed by the team at Umbraco HQ, and supported by a dedicated community of over 200,000 craftspeople globally, you can trust that Umbraco is a safe choice and is here to stay. -To view more examples, please visit [http://umbraco.com/why-umbraco/#caseStudies](http://umbraco.com/why-umbraco/#caseStudies) +To view more examples, please visit [https://umbraco.com/why-umbraco/#caseStudies](https://umbraco.com/why-umbraco/#caseStudies) + +## Why Open Source? ## +As an Open Source platform, Umbraco is more than just a CMS. We are transparent with our roadmap for future versions, our incremental sprint planning notes are publicly accessible and community contributions and packages are available for all to use. ## Downloading ## -The downloadable Umbraco releases live at [http://our.umbraco.org/download](http://our.umbraco.org/download). +The downloadable Umbraco releases live at [https://our.umbraco.org/download](https://our.umbraco.org/download). ## Forums ## -We have a forum running on [http://our.umbraco.org](http://our.umbraco.org). The discussions group on [Google Groups](https://groups.google.com/forum/#!forum/umbraco-dev) is for discussions on developing the core, and not on Umbraco-implementations or extensions in general. For those topics, please use [http://our.umbraco.org](http://our.umbraco.org). +Peer-to-peer support is available 24/7 at the community forum on [https://our.umbraco.org](https://our.umbraco.org). ## Contribute to Umbraco ## -If you want to contribute back to Umbraco you should check out our [guide to contributing](http://our.umbraco.org/contribute). +Umbraco is contribution focused and community driven. If you want to contribute back to Umbraco please check out our [guide to contributing](https://our.umbraco.org/contribute). ## Found a bug? ## -Another way you can contribute to Umbraco is by providing issue reports. For information on how to submit an issue report refer to our [online guide for reporting issues](http://our.umbraco.org/contribute/report-an-issue-or-request-a-feature). +Another way you can contribute to Umbraco is by providing issue reports. For information on how to submit an issue report refer to our [online guide for reporting issues](https://our.umbraco.org/contribute/report-an-issue-or-request-a-feature). -To view existing issues, please visit [http://issues.umbraco.org](http://issues.umbraco.org). +To view existing issues, please visit [http://issues.umbraco.org](http://issues.umbraco.org). \ No newline at end of file diff --git a/src/Umbraco.Core/ICompletable.cs b/src/Umbraco.Core/ICompletable.cs new file mode 100644 index 0000000000..594d82b0ae --- /dev/null +++ b/src/Umbraco.Core/ICompletable.cs @@ -0,0 +1,9 @@ +using System; + +namespace Umbraco.Core +{ + public interface ICompletable : IDisposable + { + void Complete(); + } +} diff --git a/src/Umbraco.Core/IO/FileSystems.cs b/src/Umbraco.Core/IO/FileSystems.cs index 497c9e57b7..0a57ad2c63 100644 --- a/src/Umbraco.Core/IO/FileSystems.cs +++ b/src/Umbraco.Core/IO/FileSystems.cs @@ -195,7 +195,7 @@ namespace Umbraco.Core.IO // _shadowEnabled = true; //} - public ShadowFileSystemsScope Shadow(Guid id) + public ICompletable Shadow(Guid id) { var typed = _wrappers.ToArray(); var wrappers = new ShadowWrapper[typed.Length + 7]; diff --git a/src/Umbraco.Core/IO/ShadowFileSystem.cs b/src/Umbraco.Core/IO/ShadowFileSystem.cs index cfa082bba2..b11b8cba96 100644 --- a/src/Umbraco.Core/IO/ShadowFileSystem.cs +++ b/src/Umbraco.Core/IO/ShadowFileSystem.cs @@ -5,7 +5,7 @@ using System.Linq; namespace Umbraco.Core.IO { - public class ShadowFileSystem : IFileSystem + internal class ShadowFileSystem : IFileSystem { private readonly IFileSystem _fs; private readonly IFileSystem _sfs; diff --git a/src/Umbraco.Core/IO/ShadowFileSystemsScope.cs b/src/Umbraco.Core/IO/ShadowFileSystemsScope.cs index e4d35eb82c..bc59317bb2 100644 --- a/src/Umbraco.Core/IO/ShadowFileSystemsScope.cs +++ b/src/Umbraco.Core/IO/ShadowFileSystemsScope.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Logging; namespace Umbraco.Core.IO { - public class ShadowFileSystemsScope : IDisposable + internal class ShadowFileSystemsScope : ICompletable { // note: taking a reference to the _manager instead of using manager.Current // to avoid using Current everywhere but really, we support only 1 scope at diff --git a/src/Umbraco.Core/IO/ShadowWrapper.cs b/src/Umbraco.Core/IO/ShadowWrapper.cs index 2c985c6ae5..64310622ca 100644 --- a/src/Umbraco.Core/IO/ShadowWrapper.cs +++ b/src/Umbraco.Core/IO/ShadowWrapper.cs @@ -5,7 +5,7 @@ using System.Linq; namespace Umbraco.Core.IO { - public class ShadowWrapper : IFileSystem + internal class ShadowWrapper : IFileSystem { private readonly IFileSystem _innerFileSystem; private readonly string _shadowPath; diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs index 277c407108..1ee409b839 100644 --- a/src/Umbraco.Core/MainDom.cs +++ b/src/Umbraco.Core/MainDom.cs @@ -95,7 +95,7 @@ namespace Umbraco.Core /// An action to execute before the AppDomain releases the main domain status. /// An optional weight (lower goes first). /// A value indicating whether it was possible to register. - /// If registering is successful, then the action + /// If registering is successful, then the action /// is guaranteed to execute before the AppDomain releases the main domain status. public bool Register(Action install, Action release, int weight = 100) { @@ -125,7 +125,7 @@ namespace Umbraco.Core try { - _logger.Debug("Stopping."); + _logger.Info("Stopping."); foreach (var callback in _callbacks.OrderBy(x => x.Key).Select(x => x.Value)) { try @@ -146,7 +146,7 @@ namespace Umbraco.Core // in any case... _isMainDom = false; _asyncLocker.Dispose(); - _logger.Debug("Released."); + _logger.Info("Released."); } } @@ -159,11 +159,11 @@ namespace Umbraco.Core // the handler is not installed so that would be the hosting environment if (_signaled) { - _logger.Debug("Cannot acquire (signaled)."); + _logger.Info("Cannot acquire (signaled)."); return false; } - _logger.Debug("Acquiring."); + _logger.Info("Acquiring."); // signal other instances that we want the lock, then wait one the lock, // which may timeout, and this is accepted - see comments below @@ -190,7 +190,7 @@ namespace Umbraco.Core HostingEnvironment.RegisterObject(this); - _logger.Debug("Acquired."); + _logger.Info("Acquired."); return true; } } diff --git a/src/Umbraco.Core/Media/UploadAutoFillProperties.cs b/src/Umbraco.Core/Media/UploadAutoFillProperties.cs index 92e1ba05b4..3a03932a72 100644 --- a/src/Umbraco.Core/Media/UploadAutoFillProperties.cs +++ b/src/Umbraco.Core/Media/UploadAutoFillProperties.cs @@ -138,7 +138,7 @@ namespace Umbraco.Core.Media using (var filestream = _mediaFileSystem.OpenFile(filepath)) { var extension = (Path.GetExtension(filepath) ?? "").TrimStart('.'); - var size = _mediaFileSystem.IsImageFile(extension) ? (Size?)_mediaFileSystem.GetDimensions(filestream) : null; + var size = _mediaFileSystem.IsImageFile(extension) ? (Size?) _mediaFileSystem.GetDimensions(filestream) : null; SetProperties(content, autoFillConfig, size, filestream.Length, extension); } } diff --git a/src/Umbraco.Core/Models/PublicAccessEntry.cs b/src/Umbraco.Core/Models/PublicAccessEntry.cs index 4af6f9536a..969e853f05 100644 --- a/src/Umbraco.Core/Models/PublicAccessEntry.cs +++ b/src/Umbraco.Core/Models/PublicAccessEntry.cs @@ -107,10 +107,7 @@ namespace Umbraco.Core.Models public void ClearRules() { - for (var i = _ruleCollection.Count - 1; i >= 0; i--) - { - RemoveRule(_ruleCollection[i]); - } + _ruleCollection.Clear(); } [DataMember] diff --git a/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs b/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs index 5847733639..c64f32ffc4 100644 --- a/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs +++ b/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs @@ -85,7 +85,7 @@ namespace Umbraco.Core.Packaging /// /// Performs the assembly scanning /// - /// + /// /// /// /// @@ -114,7 +114,7 @@ namespace Umbraco.Core.Packaging /// /// Performs the assembly scanning /// - /// + /// /// /// /// @@ -161,7 +161,7 @@ namespace Umbraco.Core.Packaging //get the list of assembly names to compare below var loadedNames = loaded.Select(x => x.GetName().Name).ToArray(); - + //Then load each referenced assembly into the context foreach (var a in loaded) { @@ -177,7 +177,7 @@ namespace Umbraco.Core.Packaging } catch (FileNotFoundException) { - //if an exception occurs it means that a referenced assembly could not be found + //if an exception occurs it means that a referenced assembly could not be found errors.Add( string.Concat("This package references the assembly '", assemblyName.Name, @@ -186,7 +186,7 @@ namespace Umbraco.Core.Packaging } catch (Exception ex) { - //if an exception occurs it means that a referenced assembly could not be found + //if an exception occurs it means that a referenced assembly could not be found errors.Add( string.Concat("This package could not be verified for compatibility. An error occurred while loading a referenced assembly '", assemblyName.Name, @@ -204,7 +204,7 @@ namespace Umbraco.Core.Packaging { //now we need to see if they contain any type 'T' var reflectedAssembly = a; - + try { var found = reflectedAssembly.GetExportedTypes() @@ -217,8 +217,8 @@ namespace Umbraco.Core.Packaging } catch (Exception ex) { - //This is a hack that nobody can seem to get around, I've read everything and it seems that - // this is quite a common thing when loading types into reflection only load context, so + //This is a hack that nobody can seem to get around, I've read everything and it seems that + // this is quite a common thing when loading types into reflection only load context, so // we're just going to ignore this specific one for now var typeLoadEx = ex as TypeLoadException; if (typeLoadEx != null) @@ -239,7 +239,7 @@ namespace Umbraco.Core.Packaging Current.Logger.Error("An error occurred scanning package assemblies", ex); } } - + } errorReport = errors.ToArray(); @@ -259,7 +259,7 @@ namespace Umbraco.Core.Packaging var contractType = contractAssemblyLoadFrom.GetExportedTypes() .FirstOrDefault(x => x.FullName == typeof(T).FullName && x.Assembly.FullName == typeof(T).Assembly.FullName); - + if (contractType == null) { throw new InvalidOperationException("Could not find type " + typeof(T) + " in the LoadFrom assemblies"); diff --git a/src/Umbraco.Core/Persistence/BulkDataReader.cs b/src/Umbraco.Core/Persistence/BulkDataReader.cs new file mode 100644 index 0000000000..8df4dd536e --- /dev/null +++ b/src/Umbraco.Core/Persistence/BulkDataReader.cs @@ -0,0 +1,1511 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Data; +using System.Data.Common; +using System.Data.SqlClient; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Core.Persistence +{ + /// + /// A base implementation of that is suitable for . + /// + /// + /// + /// Borrowed from Microsoft: + /// See: https://blogs.msdn.microsoft.com/anthonybloesch/2013/01/23/bulk-loading-data-with-idatareader-and-sqlbulkcopy/ + /// + /// This implementation is designed to be very memory efficient requiring few memory resources and to support + /// rapid transfer of data to SQL Server. + /// + /// Subclasses should implement , , + /// , , . + /// If they contain disposable resources they should override . + /// + /// SD: Alternatively, we could have used a LinqEntityDataReader which is nicer to use but it uses quite a lot of reflection and + /// I thought this would just be quicker. + /// Simple example of that: https://github.com/gridsum/DataflowEx/blob/master/Gridsum.DataflowEx/Databases/BulkDataReader.cs + /// Full example of that: https://github.com/matthewschrager/Repository/blob/master/Repository.EntityFramework/EntityDataReader.cs + /// So we know where to find that if we ever need it, these would convert any Linq data source to an IDataReader + /// + /// + internal abstract class BulkDataReader : IDataReader + { + + #region Fields + + /// + /// The containing the input row set's schema information + /// requires to function correctly. + /// + private DataTable _schemaTable = new DataTable(); + + /// + /// The mapping from the row set input to the target table's columns. + /// + private List _columnMappings = new List(); + + #endregion + + #region Subclass utility routines + + /// + /// The mapping from the row set input to the target table's columns. + /// + /// + /// If necessary, will be called to initialize the mapping. + /// + public ReadOnlyCollection ColumnMappings + { + get + { + if (this._columnMappings.Count == 0) + { + // Need to add the column definitions and mappings. + AddSchemaTableRows(); + + if (this._columnMappings.Count == 0) + { + throw new InvalidOperationException("AddSchemaTableRows did not add rows."); + } + + Debug.Assert(this._schemaTable.Rows.Count == FieldCount); + } + + return new ReadOnlyCollection(_columnMappings); + } + } + + /// + /// The name of the input row set's schema. + /// + /// + /// This may be different from the target schema but usually they are identical. + /// + protected abstract string SchemaName + { + get; + } + + /// + /// The name of the input row set's table. + /// + /// + /// This may be different from the target table but usually they are identical. + /// + protected abstract string TableName + { + get; + } + + /// + /// Adds the input row set's schema to the object. + /// + /// + /// Call + /// to do this for each row. + /// + /// + protected abstract void AddSchemaTableRows(); + + /// + /// For each , the optional columns that may have values. + /// + /// + /// This is used for checking the parameters of . + /// + /// + private static readonly Dictionary> AllowedOptionalColumnCombinations = new Dictionary> + { + { SqlDbType.BigInt, new List { } }, + { SqlDbType.Binary, new List { SchemaTableColumn.ColumnSize } }, + { SqlDbType.Bit, new List { } }, + { SqlDbType.Char, new List { SchemaTableColumn.ColumnSize } }, + { SqlDbType.Date, new List { } }, + { SqlDbType.DateTime, new List { } }, + { SqlDbType.DateTime2, new List { SchemaTableColumn.NumericPrecision } }, + { SqlDbType.DateTimeOffset, new List { SchemaTableColumn.NumericPrecision } }, + { SqlDbType.Decimal, new List { SchemaTableColumn.NumericPrecision, SchemaTableColumn.NumericScale } }, + { SqlDbType.Float, new List { SchemaTableColumn.NumericPrecision, SchemaTableColumn.NumericScale } }, + { SqlDbType.Image, new List { } }, + { SqlDbType.Int, new List { } }, + { SqlDbType.Money, new List { } }, + { SqlDbType.NChar, new List { SchemaTableColumn.ColumnSize } }, + { SqlDbType.NText, new List { } }, + { SqlDbType.NVarChar, new List { SchemaTableColumn.ColumnSize } }, + { SqlDbType.Real, new List { } }, + { SqlDbType.SmallDateTime, new List { } }, + { SqlDbType.SmallInt, new List { } }, + { SqlDbType.SmallMoney, new List { } }, + { SqlDbType.Structured, new List { } }, + { SqlDbType.Text, new List { } }, + { SqlDbType.Time, new List { SchemaTableColumn.NumericPrecision } }, + { SqlDbType.Timestamp, new List { } }, + { SqlDbType.TinyInt, new List { } }, + { SqlDbType.Udt, new List { BulkDataReader.DataTypeNameSchemaColumn } }, + { SqlDbType.UniqueIdentifier, new List { } }, + { SqlDbType.VarBinary, new List { SchemaTableColumn.ColumnSize } }, + { SqlDbType.VarChar, new List { SchemaTableColumn.ColumnSize } }, + { SqlDbType.Variant, new List { } }, + { SqlDbType.Xml, new List { BulkDataReader.XmlSchemaCollectionDatabaseSchemaColumn, BulkDataReader.XmlSchemaCollectionOwningSchemaSchemaColumn, BulkDataReader.XmlSchemaCollectionNameSchemaColumn } } + }; + + /// + /// A helper method to support . + /// + /// + /// This methds does extensive argument checks. These errors will cause hard to diagnose exceptions in latter + /// processing so it is important to detect them when they can be easily associated with the code defect. + /// + /// + /// The combination of values for the parameters is not supported. + /// + /// + /// A null value for the parameter is not supported. + /// + /// + /// The name of the column. + /// + /// + /// The size of the column which may be null if not applicable. + /// + /// + /// The precision of the column which may be null if not applicable. + /// + /// + /// The scale of the column which may be null if not applicable. + /// + /// + /// Are the column values unique (i.e. never duplicated)? + /// + /// + /// Is the column part of the primary key? + /// + /// + /// Is the column nullable (i.e. optional)? + /// + /// + /// The corresponding . + /// + /// + /// The schema name of the UDT. + /// + /// + /// The type name of the UDT. + /// + /// + /// For XML columns the schema collection's database name. Otherwise, null. + /// + /// + /// For XML columns the schema collection's schema name. Otherwise, null. + /// + /// + /// For XML columns the schema collection's name. Otherwise, null. + /// + /// + protected void AddSchemaTableRow(string columnName, + int? columnSize, + short? numericPrecision, + short? numericScale, + bool isUnique, + bool isKey, + bool allowDbNull, + SqlDbType providerType, + string udtSchema, + string udtType, + string xmlSchemaCollectionDatabase, + string xmlSchemaCollectionOwningSchema, + string xmlSchemaCollectionName) + { + if (string.IsNullOrEmpty(columnName)) + { + throw new ArgumentException("columnName must be a nonempty string."); + } + else if (columnSize.HasValue && columnSize.Value <= 0) + { + throw new ArgumentOutOfRangeException("columnSize"); + } + else if (numericPrecision.HasValue && numericPrecision.Value <= 0) + { + throw new ArgumentOutOfRangeException("numericPrecision"); + } + else if (numericScale.HasValue && numericScale.Value < 0) + { + throw new ArgumentOutOfRangeException("columnSize"); + } + + List allowedOptionalColumnList; + + if (BulkDataReader.AllowedOptionalColumnCombinations.TryGetValue(providerType, out allowedOptionalColumnList)) + { + if ((columnSize.HasValue && !allowedOptionalColumnList.Contains(SchemaTableColumn.ColumnSize)) || + (numericPrecision.HasValue && !allowedOptionalColumnList.Contains(SchemaTableColumn.NumericPrecision)) || + (numericScale.HasValue && !allowedOptionalColumnList.Contains(SchemaTableColumn.NumericScale)) || + (udtSchema != null && !allowedOptionalColumnList.Contains(BulkDataReader.DataTypeNameSchemaColumn)) || + (udtType != null && !allowedOptionalColumnList.Contains(BulkDataReader.DataTypeNameSchemaColumn)) || + (xmlSchemaCollectionDatabase != null && !allowedOptionalColumnList.Contains(BulkDataReader.XmlSchemaCollectionDatabaseSchemaColumn)) || + (xmlSchemaCollectionOwningSchema != null && !allowedOptionalColumnList.Contains(BulkDataReader.XmlSchemaCollectionOwningSchemaSchemaColumn)) || + (xmlSchemaCollectionName != null && !allowedOptionalColumnList.Contains(BulkDataReader.XmlSchemaCollectionNameSchemaColumn))) + { + throw new ArgumentException("Columns are set that are incompatible with the value of providerType."); + } + } + else + { + throw new ArgumentException("providerType is unsupported."); + } + + Type dataType; // Corresponding CLR type. + string dataTypeName; // Corresponding SQL Server type. + bool isLong = false; // Is the column a large value column (e.g. nvarchar(max))? + + switch (providerType) + { + case SqlDbType.BigInt: + dataType = typeof(long); + dataTypeName = "bigint"; + break; + + case SqlDbType.Binary: + dataType = typeof(byte[]); + + if (!columnSize.HasValue) + { + throw new ArgumentException("columnSize must be specified for \"binary\" type columns."); + } + else if (columnSize > 8000) + { + throw new ArgumentOutOfRangeException("columnSize"); + } + + dataTypeName = string.Format(CultureInfo.InvariantCulture, + "binary({0})", + columnSize.Value); + break; + + case SqlDbType.Bit: + dataType = typeof(bool); + dataTypeName = "bit"; + break; + + case SqlDbType.Char: + dataType = typeof(string); + + if (!columnSize.HasValue) + { + throw new ArgumentException("columnSize must be specified for \"char\" type columns."); + } + else if (columnSize > 8000) + { + throw new ArgumentOutOfRangeException("columnSize"); + } + + dataTypeName = string.Format(CultureInfo.InvariantCulture, + "char({0})", + columnSize.Value); + break; + + case SqlDbType.Date: + dataType = typeof(DateTime); + dataTypeName = "date"; + break; + + case SqlDbType.DateTime: + dataType = typeof(DateTime); + dataTypeName = "datetime"; + break; + + case SqlDbType.DateTime2: + dataType = typeof(DateTime); + + if (numericPrecision.HasValue) + { + if (numericPrecision.Value > 7) + { + throw new ArgumentOutOfRangeException("numericPrecision"); + } + + dataTypeName = string.Format(CultureInfo.InvariantCulture, + "datetime2({0})", + numericPrecision.Value); + } + else + { + dataTypeName = "datetime2"; + } + break; + + case SqlDbType.DateTimeOffset: + dataType = typeof(DateTimeOffset); + + if (numericPrecision.HasValue) + { + if (numericPrecision.Value > 7) + { + throw new ArgumentOutOfRangeException("numericPrecision"); + } + + dataTypeName = string.Format(CultureInfo.InvariantCulture, + "datetimeoffset({0})", + numericPrecision.Value); + } + else + { + dataTypeName = "datetimeoffset"; + } + break; + + case SqlDbType.Decimal: + dataType = typeof(decimal); + + if (!numericPrecision.HasValue || !numericScale.HasValue) + { + throw new ArgumentException("numericPrecision and numericScale must be specified for \"decimal\" type columns."); + } + else if (numericPrecision > 38) + { + throw new ArgumentOutOfRangeException("numericPrecision"); + } + else if (numericScale.Value > numericPrecision.Value) + { + throw new ArgumentException("numericScale must not be larger than numericPrecision for \"decimal\" type columns."); + } + + dataTypeName = string.Format(CultureInfo.InvariantCulture, + "decimal({0}, {1})", + numericPrecision.Value, + numericScale.Value); + break; + + case SqlDbType.Float: + dataType = typeof(double); + + if (!numericPrecision.HasValue) + { + throw new ArgumentException("numericPrecision must be specified for \"float\" type columns"); + } + else if (numericPrecision > 53) + { + throw new ArgumentOutOfRangeException("numericPrecision"); + } + + dataTypeName = string.Format(CultureInfo.InvariantCulture, + "float({0})", + numericPrecision.Value); + break; + + case SqlDbType.Image: + dataType = typeof(byte[]); + dataTypeName = "image"; + break; + + case SqlDbType.Int: + dataType = typeof(int); + dataTypeName = "int"; + break; + + case SqlDbType.Money: + dataType = typeof(decimal); + dataTypeName = "money"; + break; + + case SqlDbType.NChar: + dataType = typeof(string); + + if (!columnSize.HasValue) + { + throw new ArgumentException("columnSize must be specified for \"nchar\" type columns"); + } + else if (columnSize > 4000) + { + throw new ArgumentOutOfRangeException("columnSize"); + } + + dataTypeName = string.Format(CultureInfo.InvariantCulture, + "nchar({0})", + columnSize.Value); + break; + + case SqlDbType.NText: + dataType = typeof(string); + dataTypeName = "ntext"; + break; + + case SqlDbType.NVarChar: + dataType = typeof(string); + + if (columnSize.HasValue) + { + if (columnSize > 4000) + { + throw new ArgumentOutOfRangeException("columnSize"); + } + + dataTypeName = string.Format(CultureInfo.InvariantCulture, + "nvarchar({0})", + columnSize.Value); + } + else + { + isLong = true; + + dataTypeName = "nvarchar(max)"; + } + break; + + case SqlDbType.Real: + dataType = typeof(float); + dataTypeName = "real"; + break; + + case SqlDbType.SmallDateTime: + dataType = typeof(DateTime); + dataTypeName = "smalldatetime"; + break; + + case SqlDbType.SmallInt: + dataType = typeof(Int16); + dataTypeName = "smallint"; + break; + + case SqlDbType.SmallMoney: + dataType = typeof(decimal); + dataTypeName = "smallmoney"; + break; + + // SqlDbType.Structured not supported because it related to nested rowsets. + + case SqlDbType.Text: + dataType = typeof(string); + dataTypeName = "text"; + break; + + case SqlDbType.Time: + dataType = typeof(TimeSpan); + + if (numericPrecision.HasValue) + { + if (numericPrecision > 7) + { + throw new ArgumentOutOfRangeException("numericPrecision"); + } + + dataTypeName = string.Format(CultureInfo.InvariantCulture, + "time({0})", + numericPrecision.Value); + } + else + { + dataTypeName = "time"; + } + break; + + + // SqlDbType.Timestamp not supported because rowversions are not settable. + + case SqlDbType.TinyInt: + dataType = typeof(byte); + dataTypeName = "tinyint"; + break; + + case SqlDbType.Udt: + if (string.IsNullOrEmpty(udtSchema)) + { + throw new ArgumentException("udtSchema must be nonnull and nonempty for \"UDT\" columns."); + } + else if (string.IsNullOrEmpty(udtType)) + { + throw new ArgumentException("udtType must be nonnull and nonempty for \"UDT\" columns."); + } + + dataType = typeof(object); + using (SqlCommandBuilder commandBuilder = new SqlCommandBuilder()) + { + dataTypeName = commandBuilder.QuoteIdentifier(udtSchema) + "." + commandBuilder.QuoteIdentifier(udtType); + } + break; + + case SqlDbType.UniqueIdentifier: + dataType = typeof(Guid); + dataTypeName = "uniqueidentifier"; + break; + + case SqlDbType.VarBinary: + dataType = typeof(byte[]); + + if (columnSize.HasValue) + { + if (columnSize > 8000) + { + throw new ArgumentOutOfRangeException("columnSize"); + } + + dataTypeName = string.Format(CultureInfo.InvariantCulture, + "varbinary({0})", + columnSize.Value); + } + else + { + isLong = true; + + dataTypeName = "varbinary(max)"; + } + break; + + case SqlDbType.VarChar: + dataType = typeof(string); + + if (columnSize.HasValue) + { + if (columnSize > 8000) + { + throw new ArgumentOutOfRangeException("columnSize"); + } + + dataTypeName = string.Format(CultureInfo.InvariantCulture, + "varchar({0})", + columnSize.Value); + } + else + { + isLong = true; + + dataTypeName = "varchar(max)"; + } + break; + + case SqlDbType.Variant: + dataType = typeof(object); + dataTypeName = "sql_variant"; + break; + + case SqlDbType.Xml: + dataType = typeof(string); + + if (xmlSchemaCollectionName == null) + { + if (xmlSchemaCollectionDatabase != null || xmlSchemaCollectionOwningSchema != null) + { + throw new ArgumentException("xmlSchemaCollectionDatabase and xmlSchemaCollectionOwningSchema must be null if xmlSchemaCollectionName is null for \"xml\" columns."); + } + + dataTypeName = "xml"; + } + else + { + if (xmlSchemaCollectionName.Length == 0) + { + throw new ArgumentException("xmlSchemaCollectionName must be nonempty or null for \"xml\" columns."); + } + else if (xmlSchemaCollectionDatabase != null && + xmlSchemaCollectionDatabase.Length == 0) + { + throw new ArgumentException("xmlSchemaCollectionDatabase must be null or nonempty for \"xml\" columns."); + } + else if (xmlSchemaCollectionOwningSchema != null && + xmlSchemaCollectionOwningSchema.Length == 0) + { + throw new ArgumentException("xmlSchemaCollectionOwningSchema must be null or nonempty for \"xml\" columns."); + } + + System.Text.StringBuilder schemaCollection = new System.Text.StringBuilder("xml("); + + if (xmlSchemaCollectionDatabase != null) + { + schemaCollection.Append("[" + xmlSchemaCollectionDatabase + "]"); + } + + schemaCollection.Append("[" + (xmlSchemaCollectionOwningSchema == null ? SchemaName : xmlSchemaCollectionOwningSchema) + "]"); + schemaCollection.Append("[" + xmlSchemaCollectionName + "]"); + + dataTypeName = schemaCollection.ToString(); + } + break; + + default: + throw new ArgumentOutOfRangeException("providerType"); + + } + + this._schemaTable.Rows.Add(columnName, + _schemaTable.Rows.Count, + columnSize, + numericPrecision, + numericScale, + isUnique, + isKey, + "TraceServer", + "TraceWarehouse", + columnName, + SchemaName, + TableName, + dataType, + allowDbNull, + providerType, + false, // isAliased + false, // isExpression + false, // isIdentity, + false, // isAutoIncrement, + false, // isRowVersion, + false, // isHidden, + isLong, + true, // isReadOnly, + dataType, + dataTypeName, + xmlSchemaCollectionDatabase, + xmlSchemaCollectionOwningSchema, + xmlSchemaCollectionName); + + this._columnMappings.Add(new SqlBulkCopyColumnMapping(columnName, columnName)); + } + + #endregion + + #region Constructors + + private const string IsIdentitySchemaColumn = "IsIdentity"; + + private const string DataTypeNameSchemaColumn = "DataTypeName"; + + private const string XmlSchemaCollectionDatabaseSchemaColumn = "XmlSchemaCollectionDatabase"; + + private const string XmlSchemaCollectionOwningSchemaSchemaColumn = "XmlSchemaCollectionOwningSchema"; + + private const string XmlSchemaCollectionNameSchemaColumn = "XmlSchemaCollectionName"; + + /// + /// Constructor. + /// + protected BulkDataReader() + { + this._schemaTable.Locale = System.Globalization.CultureInfo.InvariantCulture; + + DataColumnCollection columns = _schemaTable.Columns; + + columns.Add(SchemaTableColumn.ColumnName, typeof(System.String)); + columns.Add(SchemaTableColumn.ColumnOrdinal, typeof(System.Int32)); + columns.Add(SchemaTableColumn.ColumnSize, typeof(System.Int32)); + columns.Add(SchemaTableColumn.NumericPrecision, typeof(System.Int16)); + columns.Add(SchemaTableColumn.NumericScale, typeof(System.Int16)); + columns.Add(SchemaTableColumn.IsUnique, typeof(System.Boolean)); + columns.Add(SchemaTableColumn.IsKey, typeof(System.Boolean)); + columns.Add(SchemaTableOptionalColumn.BaseServerName, typeof(System.String)); + columns.Add(SchemaTableOptionalColumn.BaseCatalogName, typeof(System.String)); + columns.Add(SchemaTableColumn.BaseColumnName, typeof(System.String)); + columns.Add(SchemaTableColumn.BaseSchemaName, typeof(System.String)); + columns.Add(SchemaTableColumn.BaseTableName, typeof(System.String)); + columns.Add(SchemaTableColumn.DataType, typeof(System.Type)); + columns.Add(SchemaTableColumn.AllowDBNull, typeof(System.Boolean)); + columns.Add(SchemaTableColumn.ProviderType, typeof(System.Int32)); + columns.Add(SchemaTableColumn.IsAliased, typeof(System.Boolean)); + columns.Add(SchemaTableColumn.IsExpression, typeof(System.Boolean)); + columns.Add(BulkDataReader.IsIdentitySchemaColumn, typeof(System.Boolean)); + columns.Add(SchemaTableOptionalColumn.IsAutoIncrement, typeof(System.Boolean)); + columns.Add(SchemaTableOptionalColumn.IsRowVersion, typeof(System.Boolean)); + columns.Add(SchemaTableOptionalColumn.IsHidden, typeof(System.Boolean)); + columns.Add(SchemaTableColumn.IsLong, typeof(System.Boolean)); + columns.Add(SchemaTableOptionalColumn.IsReadOnly, typeof(System.Boolean)); + columns.Add(SchemaTableOptionalColumn.ProviderSpecificDataType, typeof(System.Type)); + columns.Add(BulkDataReader.DataTypeNameSchemaColumn, typeof(System.String)); + columns.Add(BulkDataReader.XmlSchemaCollectionDatabaseSchemaColumn, typeof(System.String)); + columns.Add(BulkDataReader.XmlSchemaCollectionOwningSchemaSchemaColumn, typeof(System.String)); + columns.Add(BulkDataReader.XmlSchemaCollectionNameSchemaColumn, typeof(System.String)); + } + + #endregion + + #region IDataReader + + /// + /// Gets a value indicating the depth of nesting for the current row. (Inherited from .) + /// + /// + /// does not support nested result sets so this method always returns 0. + /// + /// + public int Depth + { + get { return 0; } + } + + /// + /// Gets the number of columns in the current row. (Inherited from .) + /// + /// + public int FieldCount + { + get { return GetSchemaTable().Rows.Count; } + } + + /// + /// Is the bulk copy process open? + /// + bool _isOpen = true; + + /// + /// Gets a value indicating whether the data reader is closed. (Inherited from .) + /// + /// + public bool IsClosed + { + get { return !_isOpen; } + } + + /// + /// Gets the column located at the specified index. (Inherited from .) + /// + /// + /// No column with the specified index was found. + /// + /// + /// The zero-based index of the column to get. + /// + /// + /// The column located at the specified index as an . + /// + /// + public object this[int i] + { + get { return GetValue(i); } + } + + /// + /// Gets the column with the specified name. (Inherited from .) + /// + /// + /// No column with the specified name was found. + /// + /// + /// The name of the column to find. + /// + /// + /// The column located at the specified name as an . + /// + /// + public object this[string name] + { + get { return GetValue(GetOrdinal(name)); } + } + + /// + /// Gets the number of rows changed, inserted, or deleted by execution of the SQL statement. (Inherited from .) + /// + /// + /// Always returns -1 which is the expected behaviour for statements. + /// + /// + public virtual int RecordsAffected + { + get { return -1; } + } + + /// + /// Closes the . (Inherited from .) + /// + /// + public void Close() + { + this._isOpen = false; + } + + /// + /// Gets the value of the specified column as a . (Inherited from .) + /// + /// + /// The index passed was outside the range of 0 through . + /// + /// + /// The zero-based column ordinal. + /// + /// + /// The value of the column. + /// + /// + public bool GetBoolean(int i) + { + return (bool)GetValue(i); + } + + /// + /// Gets the value of the specified column as a . (Inherited from .) + /// + /// + /// The index passed was outside the range of 0 through . + /// + /// + /// The zero-based column ordinal. + /// + /// + /// The value of the column. + /// + /// + public byte GetByte(int i) + { + return (byte)GetValue(i); + } + + /// + /// Reads a stream of bytes from the specified column offset into the buffer as an array, starting at the given buffer offset. + /// (Inherited from .) + /// + /// + /// If you pass a buffer that is null, returns the length of the row in bytes. + /// + /// + /// The index passed was outside the range of 0 through . + /// + /// + /// The zero-based column ordinal. + /// + /// + /// The index within the field from which to start the read operation. + /// + /// + /// The buffer into which to read the stream of bytes. + /// + /// + /// The index for buffer to start the read operation. + /// + /// + /// The number of bytes to read. + /// + /// + /// The actual number of bytes read. + /// + /// + public long GetBytes(int i, + long fieldOffset, + byte[] buffer, + int bufferoffset, + int length) + { + byte[] data = (byte[])GetValue(i); + + if (buffer != null) + { + Array.Copy(data, fieldOffset, buffer, bufferoffset, length); + } + + return data.LongLength; + } + + /// + /// Gets the value of the specified column as a . (Inherited from .) + /// + /// + /// The index passed was outside the range of 0 through . + /// + /// + /// The zero-based column ordinal. + /// + /// + /// The value of the column. + /// + /// + public char GetChar(int i) + { + char result; + + object data = GetValue(i); + char? dataAsChar = data as char?; + char[] dataAsCharArray = data as char[]; + string dataAsString = data as string; + + if (dataAsChar.HasValue) + { + result = dataAsChar.Value; + } + else if (dataAsCharArray != null && + dataAsCharArray.Length == 1) + { + result = dataAsCharArray[0]; + } + else if (dataAsString != null && + dataAsString.Length == 1) + { + result = dataAsString[0]; + } + else + { + throw new InvalidOperationException("GetValue did not return a Char compatible type."); + } + + return result; + } + + /// + /// Reads a stream of characters from the specified column offset into the buffer as an array, starting at the given buffer offset. + /// (Inherited from .) + /// + /// + /// If you pass a buffer that is null, returns the length of the row in bytes. + /// + /// + /// The index passed was outside the range of 0 through . + /// + /// + /// The zero-based column ordinal. + /// + /// + /// The index within the field from which to start the read operation. + /// + /// + /// The buffer into which to read the stream of characters. + /// + /// + /// The index for buffer to start the read operation. + /// + /// + /// The number of characters to read. + /// + /// + /// The actual number of characters read. + /// + /// + public long GetChars(int i, + long fieldoffset, + char[] buffer, + int bufferoffset, + int length) + { + object data = GetValue(i); + + string dataAsString = data as string; + char[] dataAsCharArray = data as char[]; + + if (dataAsString != null) + { + dataAsCharArray = dataAsString.ToCharArray((int)fieldoffset, length); + } + else if (dataAsCharArray == null) + { + throw new InvalidOperationException("GetValue did not return either a Char array or a String."); + } + + if (buffer != null) + { + Array.Copy(dataAsCharArray, fieldoffset, buffer, bufferoffset, length); + } + + return dataAsCharArray.LongLength; + } + + /// + /// Returns an IDataReader for the specified column ordinal. (Inherited from .) + /// + /// + /// does not support nested result sets so this method always returns null. + /// + /// + /// The index passed was outside the range of 0 through . + /// + /// + /// The zero-based column ordinal. + /// + /// + /// The for the specified column ordinal (null). + /// + /// + public IDataReader GetData(int i) + { + if (i < 0 || i >= this.FieldCount) + { + throw new ArgumentOutOfRangeException("i"); + } + + return null; + } + + /// + /// The data type information for the specified field. (Inherited from .) + /// + /// + /// The index passed was outside the range of 0 through . + /// + /// + /// The zero-based column ordinal. + /// + /// + /// The data type information for the specified field. + /// + /// + public string GetDataTypeName(int i) + { + return GetFieldType(i).Name; + } + + /// + /// Gets the value of the specified column as a . (Inherited from .) + /// + /// + /// The index passed was outside the range of 0 through . + /// + /// + /// The zero-based column ordinal. + /// + /// + /// The value of the column. + /// + /// + public DateTime GetDateTime(int i) + { + return (DateTime)GetValue(i); + } + + /// + /// Gets the value of the specified column as a . + /// + /// + /// The index passed was outside the range of 0 through . + /// + /// + /// The zero-based column ordinal. + /// + /// + /// The value of the column. + /// + public DateTimeOffset GetDateTimeOffset(int i) + { + return (DateTimeOffset)GetValue(i); + } + + /// + /// Gets the value of the specified column as a . (Inherited from .) + /// + /// + /// The index passed was outside the range of 0 through . + /// + /// + /// The zero-based column ordinal. + /// + /// + /// The value of the column. + /// + /// + public decimal GetDecimal(int i) + { + return (Decimal)GetValue(i); + } + + /// + /// Gets the value of the specified column as a . (Inherited from .) + /// + /// + /// The index passed was outside the range of 0 through . + /// + /// + /// The zero-based column ordinal. + /// + /// + /// The value of the column. + /// + /// + public double GetDouble(int i) + { + return (double)GetValue(i); + } + + /// + /// Gets the information corresponding to the type of that would be returned from . + /// (Inherited from .) + /// + /// + /// The index passed was outside the range of 0 through . + /// + /// + /// The zero-based column ordinal. + /// + /// + /// The information corresponding to the type of that would be returned from . + /// + /// + public Type GetFieldType(int i) + { + return (Type)GetSchemaTable().Rows[i][SchemaTableColumn.DataType]; + } + + /// + /// Gets the value of the specified column as a . (Inherited from .) + /// + /// + /// The index passed was outside the range of 0 through . + /// + /// + /// The zero-based column ordinal. + /// + /// + /// The value of the column. + /// + /// + public float GetFloat(int i) + { + return (float)this[i]; + } + + /// + /// Gets the value of the specified column as a . (Inherited from .) + /// + /// + /// The index passed was outside the range of 0 through . + /// + /// + /// The zero-based column ordinal. + /// + /// + /// The value of the column. + /// + /// + public Guid GetGuid(int i) + { + return (Guid)GetValue(i); + } + + /// + /// Gets the value of the specified column as a . (Inherited from .) + /// + /// + /// The index passed was outside the range of 0 through . + /// + /// + /// The zero-based column ordinal. + /// + /// + /// The value of the column. + /// + /// + public short GetInt16(int i) + { + return (short)GetValue(i); + } + + /// + /// Gets the value of the specified column as a . (Inherited from .) + /// + /// + /// The index passed was outside the range of 0 through . + /// + /// + /// The zero-based column ordinal. + /// + /// + /// The value of the column. + /// + /// + public int GetInt32(int i) + { + return (int)GetValue(i); + } + + /// + /// Gets the value of the specified column as a . (Inherited from .) + /// + /// + /// The index passed was outside the range of 0 through . + /// + /// + /// The zero-based column ordinal. + /// + /// + /// The value of the column. + /// + /// + public long GetInt64(int i) + { + return (long)GetValue(i); + } + + /// + /// Gets the name for the field to find. (Inherited from .) + /// + /// + /// The index passed was outside the range of 0 through . + /// + /// + /// The zero-based column ordinal. + /// + /// + /// The name of the field or the empty string (""), if there is no value to return. + /// + /// + public string GetName(int i) + { + return (string)GetSchemaTable().Rows[i][SchemaTableColumn.ColumnName]; + } + + /// + /// Return the index of the named field. (Inherited from .) + /// + /// + /// The index of the named field was not found. + /// + /// + /// The name of the field to find. + /// + /// + /// The index of the named field. + /// + /// + public int GetOrdinal(string name) + { + if (name == null) // Empty strings are handled as a IndexOutOfRangeException. + { + throw new ArgumentNullException("name"); + } + + int result = -1; + + int rowCount = FieldCount; + + DataRowCollection schemaRows = GetSchemaTable().Rows; + + // Case sensitive search + for (int ordinal = 0; ordinal < rowCount; ordinal++) + { + if (String.Equals((string)schemaRows[ordinal][SchemaTableColumn.ColumnName], name, StringComparison.Ordinal)) + { + result = ordinal; + } + } + + if (result == -1) + { + // Case insensitive search. + for (int ordinal = 0; ordinal < rowCount; ordinal++) + { + if (String.Equals((string)schemaRows[ordinal][SchemaTableColumn.ColumnName], name, StringComparison.OrdinalIgnoreCase)) + { + result = ordinal; + } + } + } + + if (result == -1) + { + throw new IndexOutOfRangeException(name); + } + + return result; + } + + /// + /// Returns a that describes the column metadata of the . (Inherited from .) + /// + /// + /// The is closed. + /// + /// + /// A that describes the column metadata. + /// + /// + public DataTable GetSchemaTable() + { + if (IsClosed) + { + throw new InvalidOperationException("The IDataReader is closed."); + } + + if (_schemaTable.Rows.Count == 0) + { + // Need to add the column definitions and mappings + _schemaTable.TableName = TableName; + + AddSchemaTableRows(); + + Debug.Assert(_schemaTable.Rows.Count == FieldCount); + } + + return _schemaTable; + } + + /// + /// Gets the value of the specified column as a . (Inherited from .) + /// + /// + /// The index passed was outside the range of 0 through . + /// + /// + /// The zero-based column ordinal. + /// + /// + /// The value of the column. + /// + /// + public string GetString(int i) + { + return (string)GetValue(i); + } + + /// + /// Gets the value of the specified column as a . + /// + /// + /// The index passed was outside the range of 0 through . + /// + /// + /// The zero-based column ordinal. + /// + /// + /// The value of the column. + /// + public TimeSpan GetTimeSpan(int i) + { + return (TimeSpan)GetValue(i); + } + + /// + /// Gets the value of the specified column as a . (Inherited from .) + /// + /// + /// The index passed was outside the range of 0 through . + /// + /// + /// The zero-based column ordinal. + /// + /// + /// The value of the column. + /// + /// + public abstract object GetValue(int i); + + /// + /// Populates an array of objects with the column values of the current record. (Inherited from .) + /// + /// + /// was null. + /// + /// + /// An array of to copy the attribute fields into. + /// + /// + /// The number of instances of in the array. + /// + /// + public int GetValues(object[] values) + { + if (values == null) + { + throw new ArgumentNullException("values"); + } + + int fieldCount = Math.Min(FieldCount, values.Length); + + for (int i = 0; i < fieldCount; i++) + { + values[i] = GetValue(i); + } + + return fieldCount; + } + + /// + /// Return whether the specified field is set to null. (Inherited from .) + /// + /// + /// The index passed was outside the range of 0 through . + /// + /// + /// The zero-based column ordinal. + /// + /// + /// True if the specified field is set to null; otherwise, false. + /// + /// + public bool IsDBNull(int i) + { + object data = GetValue(i); + + return data == null || Convert.IsDBNull(data); + } + + /// + /// Advances the data reader to the next result, when reading the results of batch SQL statements. (Inherited from .) + /// + /// + /// for returns a single result set so false is always returned. + /// + /// + /// True if there are more rows; otherwise, false. for returns a single result set so false is always returned. + /// + /// + public bool NextResult() + { + return false; + } + + /// + /// Advances the to the next record. (Inherited from .) + /// + /// + /// True if there are more rows; otherwise, false. + /// + /// + public abstract bool Read(); + + #endregion + + #region IDisposable + + /// + /// Has the object been disposed? + /// + bool _disposed = false; + + /// + /// Dispose of any disposable and expensive resources. + /// + /// + /// Is this call the result of a call? + /// + protected virtual void Dispose(bool disposing) + { + if (!this._disposed) + { + this._disposed = true; + + if (disposing) + { + if (_schemaTable != null) + { + _schemaTable.Dispose(); + this._schemaTable = null; + } + + this._columnMappings = null; + + this._isOpen = false; + + GC.SuppressFinalize(this); + } + } + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. (Inherited from .) + /// + /// + public void Dispose() + { + Dispose(true); + } + + /// + /// Finalizer + /// + /// + /// has no unmanaged resources but a subclass may thus a finalizer is required. + /// + ~BulkDataReader() + { + Dispose(false); + } + + #endregion + + } +} diff --git a/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions.cs b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions.cs index 5cabb4ad30..a6292f9262 100644 --- a/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions.cs +++ b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions.cs @@ -3,9 +3,11 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Data.SqlClient; +using System.Data.SqlServerCe; using System.Linq; using System.Text.RegularExpressions; using NPoco; +using StackExchange.Profiling.Data; using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence @@ -158,137 +160,270 @@ namespace Umbraco.Core.Persistence // todo: review NPoco native InsertBulk to replace the code below - public static void BulkInsertRecordsWithTransaction(this IDatabase db, ISqlSyntaxProvider sqlSyntax, IEnumerable records) + /// + /// Bulk-inserts records within a transaction. + /// + /// The type of the records. + /// The database. + /// The records. + /// Whether to use native bulk insert when available. + public static void BulkInsertRecordsWithTransaction(this Database database, IEnumerable records, bool useNativeBulkInsert = true) { var recordsA = records.ToArray(); if (recordsA.Length == 0) return; // no need to "try...catch", if the transaction is not completed it will rollback! - using (var tr = db.GetTransaction()) + using (var tr = database.GetTransaction()) { - db.BulkInsertRecords(sqlSyntax, recordsA); + database.BulkInsertRecords(recordsA, useNativeBulkInsert); tr.Complete(); } } /// - /// Performs the bulk insertion in the context of a current transaction with an optional parameter to complete the transaction - /// when finished + /// Bulk-inserts records. /// - /// - /// - /// - /// - public static void BulkInsertRecords(this IDatabase db, ISqlSyntaxProvider sqlSyntax, IEnumerable records) + /// The type of the records. + /// The database. + /// The records. + /// Whether to use native bulk insert when available. + /// The number of records that were inserted. + public static int BulkInsertRecords(this Database database, IEnumerable records, bool useNativeBulkInsert = true) { var recordsA = records.ToArray(); - if (recordsA.Length == 0) - return; + if (recordsA.Length == 0) return 0; - // if it is sql ce or it is a sql server version less than 2008, we need to do individual inserts. - var sqlServerSyntax = sqlSyntax as SqlServerSyntaxProvider; - if ((sqlServerSyntax != null && (int) sqlServerSyntax.ServerVersion.ProductVersionName < (int) SqlServerSyntaxProvider.VersionName.V2008) - || sqlSyntax is SqlCeSyntaxProvider) - { - // SqlCe doesn't support bulk insert statements! - foreach (var poco in recordsA) - db.Insert(poco); - } - else - { - string[] sqlStatements; - var cmds = db.GenerateBulkInsertCommand(recordsA, db.Connection, out sqlStatements); - for (var i = 0; i < sqlStatements.Length; i++) - { - using (var cmd = cmds[i]) - { - cmd.CommandText = sqlStatements[i]; - cmd.ExecuteNonQuery(); - } - } - } - } + var pocoData = database.PocoDataFactory.ForType(typeof(T)); + if (pocoData == null) throw new InvalidOperationException("Could not find PocoData for " + typeof(T)); - private static bool IncludeColumn(PocoData pocoData, string columnKey, PocoColumn column) - { - // exclude result columns, - // exclude primary key column if auto-increment - return column.ResultColumn == false && (pocoData.TableInfo.AutoIncrement == false || columnKey != pocoData.TableInfo.PrimaryKey); + if (database.DatabaseType.IsSqlCe()) + { + return useNativeBulkInsert + ? BulkInsertRecordsSqlCe(database, pocoData, recordsA) + : BulkInsertRecordsWithCommands(database, recordsA); + } + + if (database.DatabaseType.IsSqlServer()) + { + return useNativeBulkInsert && database.DatabaseType.IsSqlServer2008OrLater() + ? BulkInsertRecordsSqlServer(database, pocoData, recordsA) + : BulkInsertRecordsWithCommands(database, recordsA); + } + + if (database.DatabaseType.IsMySql()) + return BulkInsertRecordsWithCommands(database, recordsA); + + throw new NotSupportedException(); } /// - /// Creates a bulk insert command + /// Bulk-insert records using commands. /// - /// - /// - /// - /// - /// - /// Sql commands with populated command parameters required to execute the sql statement - /// - /// The limits for number of parameters are 2100 (in sql server, I think there's many more allowed in mysql). So - /// we need to detect that many params and split somehow. - /// For some reason the 2100 limit is not actually allowed even though the exception from sql server mentions 2100 as a max, perhaps it is 2099 - /// that is max. I've reduced it to 2000 anyways. - /// - internal static IDbCommand[] GenerateBulkInsertCommand( - this IDatabase db, - T[] records, - DbConnection connection, - out string[] sql) + /// The type of the records. + /// The database. + /// The records. + /// The number of records that were inserted. + private static int BulkInsertRecordsWithCommands(Database database, T[] records) { - var pocoData = db.PocoDataFactory.ForType(typeof(T)); + foreach (var command in database.GenerateBulkInsertCommands(records)) + command.ExecuteNonQuery(); + + return records.Length; // what else? + } + + /// + /// Creates bulk-insert commands. + /// + /// The type of the records. + /// The database. + /// The records. + /// The sql commands to execute. + internal static IDbCommand[] GenerateBulkInsertCommands(this Database database, T[] records) + { + var pocoData = database.PocoDataFactory.ForType(typeof(T)); // get columns to include, = number of parameters per row - var columns = pocoData.Columns.Where(c => IncludeColumn(pocoData, c.Key, c.Value)).ToArray(); - var paramsPerRow = columns.Length; + var columns = pocoData.Columns.Where(c => IncludeColumn(pocoData, c)).ToArray(); + var paramsPerRecord = columns.Length; // format columns to sql - var tableName = db.DatabaseType.EscapeTableName(pocoData.TableInfo.TableName); - var columnNames = string.Join(", ", columns.Select(c => tableName + "." + db.DatabaseType.EscapeSqlIdentifier(c.Key))); + var tableName = database.DatabaseType.EscapeTableName(pocoData.TableInfo.TableName); + var columnNames = string.Join(", ", columns.Select(c => tableName + "." + database.DatabaseType.EscapeSqlIdentifier(c.Key))); - // example calc: - // given: we have 4168 items in the collection, each item contains 8 command parameters (values to be inserted) - // 2100 / 8 = 262.5 - // Math.Floor(2100 / 8) = 262 items per trans - // 4168 / 262 = 15.908... = there will be 16 trans in total + // example: + // assume 4168 records, each record containing 8 fields, ie 8 command parameters + // max 2100 parameter per command + // Math.Floor(2100 / 8) = 262 record per command + // 4168 / 262 = 15.908... = there will be 16 command in total + // (if we have disabled db parameters, then all records will be included, in only one command) + var recordsPerCommand = paramsPerRecord == 0 ? int.MaxValue : Convert.ToInt32(Math.Floor(2000.00 / paramsPerRecord)); + var commandsCount = Convert.ToInt32(Math.Ceiling((double)records.Length / recordsPerCommand)); - // if we have disabled db parameters, then all items will be included, in only one transaction - var rowsPerCommand = paramsPerRow == 0 ? int.MaxValue : Convert.ToInt32(Math.Floor(2000.00 / paramsPerRow)); - var commandsCount = Convert.ToInt32(Math.Ceiling((double) records.Length / rowsPerCommand)); - - sql = new string[commandsCount]; var commands = new IDbCommand[commandsCount]; - + var recordsIndex = 0; + var recordsLeftToInsert = records.Length; + var prefix = database.DatabaseType.GetParameterPrefix(database.ConnectionString); for (var commandIndex = 0; commandIndex < commandsCount; commandIndex++) { - var itemsForTrans = records - .Skip(commandIndex * rowsPerCommand) - .Take(rowsPerCommand); - - var cmd = db.CreateCommand(connection, CommandType.Text, ""); - var prefix = db.DatabaseType.GetParameterPrefix(cmd.Connection.ConnectionString); - var pocoValues = new List(); - var index = 0; - foreach (var poco in itemsForTrans) + var command = database.CreateCommand(database.Connection, CommandType.Text, string.Empty); + var parameterIndex = 0; + var commandRecords = Math.Min(recordsPerCommand, recordsLeftToInsert); + var recordsValues = new string[commandRecords]; + for (var commandRecordIndex = 0; commandRecordIndex < commandRecords; commandRecordIndex++, recordsIndex++, recordsLeftToInsert--) { - var values = new List(); - foreach (var column in columns) + var record = records[recordsIndex]; + var recordValues = new string[columns.Length]; + for (var columnIndex = 0; columnIndex < columns.Length; columnIndex++) { - db.AddParameter(cmd, column.Value.GetValue(poco)); - values.Add(prefix + index++); + database.AddParameter(command, columns[columnIndex].Value.GetValue(record)); + recordValues[columnIndex] = prefix + parameterIndex++; } - pocoValues.Add("(" + string.Join(",", values.ToArray()) + ")"); + recordsValues[commandRecordIndex] = "(" + string.Join(",", recordValues) + ")"; } - sql[commandIndex] = $"INSERT INTO {tableName} ({columnNames}) VALUES {string.Join(", ", pocoValues)}"; - commands[commandIndex] = cmd; + command.CommandText = $"INSERT INTO {tableName} ({columnNames}) VALUES {string.Join(", ", recordsValues)}"; + commands[commandIndex] = command; } return commands; } + /// + /// Determines whether a column should be part of a bulk-insert. + /// + /// The PocoData object corresponding to the record's type. + /// The column. + /// A value indicating whether the column should be part of the bulk-insert. + /// Columns that are primary keys and auto-incremental, or result columns, are excluded from bulk-inserts. + private static bool IncludeColumn(PocoData pocoData, KeyValuePair column) + { + return column.Value.ResultColumn == false + && (pocoData.TableInfo.AutoIncrement == false || column.Key != pocoData.TableInfo.PrimaryKey); + } + + /// + /// Bulk-insert records using SqlCE TableDirect method. + /// + /// The type of the records. + /// The database. + /// The PocoData object corresponding to the record's type. + /// The records. + /// The number of records that were inserted. + internal static int BulkInsertRecordsSqlCe(Database database, PocoData pocoData, IEnumerable records) + { + var columns = pocoData.Columns.ToArray(); + + // create command against the original database.Connection + using (var command = database.CreateCommand(database.Connection, CommandType.TableDirect, string.Empty)) + { + command.CommandText = pocoData.TableInfo.TableName; + // fixme - not supporting transactions? + //cmd.Transaction = GetTypedTransaction(db.Connection.); + + // fixme - why the double 'using' here? + var count = 0; + using (var tCommand = GetTypedCommand(command)) // execute on the real command + { + // seems to cause problems, I think this is primarily used for retrieval, not inserting. + // see: https://msdn.microsoft.com/en-us/library/system.data.sqlserverce.sqlcecommand.indexname%28v=vs.100%29.aspx?f=255&MSPPError=-2147217396 + //sqlCeCommand.IndexName = pd.TableInfo.PrimaryKey; + + using (var resultSet = tCommand.ExecuteResultSet(ResultSetOptions.Updatable)) + { + var updatableRecord = resultSet.CreateRecord(); + foreach (var record in records) + { + for (var i = 0; i < columns.Length; i++) + { + // skip the index if this shouldn't be included (i.e. PK) + if (IncludeColumn(pocoData, columns[i])) + { + var val = columns[i].Value.GetValue(record); + updatableRecord.SetValue(i, val); + } + } + resultSet.Insert(updatableRecord); + count++; + } + } + } + + return count; + } + } + + /// + /// Bulk-insert records using SqlServer BulkCopy method. + /// + /// The type of the records. + /// The database. + /// The PocoData object corresponding to the record's type. + /// The records. + /// The number of records that were inserted. + internal static int BulkInsertRecordsSqlServer(Database database, PocoData pocoData, IEnumerable records) + { + // create command against the original database.Connection + using (var command = database.CreateCommand(database.Connection, CommandType.Text, string.Empty)) + { + // use typed connection and transactionf or SqlBulkCopy + var tConnection = GetTypedConnection(database.Connection); + var tTransaction = GetTypedTransaction(command.Transaction); + var tableName = pocoData.TableInfo.TableName; + + var umbracoDatabase = database as UmbracoDatabase; + if (umbracoDatabase == null) throw new NotSupportedException("Database must be UmbracoDatabase."); + var syntax = umbracoDatabase.SqlSyntax as SqlServerSyntaxProvider; + if (syntax == null) throw new NotSupportedException("SqlSyntax must be SqlServerSyntaxProvider."); + + using (var copy = new SqlBulkCopy(tConnection, SqlBulkCopyOptions.Default, tTransaction) { BulkCopyTimeout = 10000, DestinationTableName = tableName }) + using (var bulkReader = new PocoDataDataReader(records, pocoData, syntax)) + { + copy.WriteToServer(bulkReader); + return bulkReader.RecordsAffected; + } + } + } + + /// + /// Returns the underlying connection as a typed connection - this is used to unwrap the profiled mini profiler stuff + /// + /// + /// + /// + private static TConnection GetTypedConnection(IDbConnection connection) + where TConnection : class, IDbConnection + { + var profiled = connection as ProfiledDbConnection; + return profiled == null ? connection as TConnection : profiled.InnerConnection as TConnection; + } + + /// + /// Returns the underlying transaction as a typed transaction - this is used to unwrap the profiled mini profiler stuff + /// + /// + /// + /// + private static TTransaction GetTypedTransaction(IDbTransaction transaction) + where TTransaction : class, IDbTransaction + { + var profiled = transaction as ProfiledDbTransaction; + return profiled == null ? transaction as TTransaction : profiled.WrappedTransaction as TTransaction; + } + + /// + /// Returns the underlying command as a typed command - this is used to unwrap the profiled mini profiler stuff + /// + /// + /// + /// + private static TCommand GetTypedCommand(IDbCommand command) + where TCommand : class, IDbCommand + { + var profiled = command as ProfiledDbCommand; + return profiled == null ? command as TCommand : profiled.InternalCommand as TCommand; + } + public static void TruncateTable(this IDatabase db, ISqlSyntaxProvider sqlSyntax, string tableName) { var sql = new Sql(string.Format( diff --git a/src/Umbraco.Core/Persistence/NPocoDatabaseTypeExtensions.cs b/src/Umbraco.Core/Persistence/NPocoDatabaseTypeExtensions.cs index b089110193..e40e6dedd3 100644 --- a/src/Umbraco.Core/Persistence/NPocoDatabaseTypeExtensions.cs +++ b/src/Umbraco.Core/Persistence/NPocoDatabaseTypeExtensions.cs @@ -17,6 +17,11 @@ namespace Umbraco.Core.Persistence return databaseType is NPoco.DatabaseTypes.SqlServerDatabaseType; } + public static bool IsSqlServer2008OrLater(this DatabaseType databaseType) + { + return databaseType is NPoco.DatabaseTypes.SqlServer2008DatabaseType; + } + public static bool IsSqlCe(this DatabaseType databaseType) { return databaseType is NPoco.DatabaseTypes.SqlServerCEDatabaseType; diff --git a/src/Umbraco.Core/Persistence/PocoDataDataReader.cs b/src/Umbraco.Core/Persistence/PocoDataDataReader.cs new file mode 100644 index 0000000000..e1b3f4e88e --- /dev/null +++ b/src/Umbraco.Core/Persistence/PocoDataDataReader.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence +{ + /// + /// A data reader used for reading collections of PocoData entity types + /// + /// + /// We are using a custom data reader so that tons of memory is not consumed when rebuilding this table, previously + /// we'd generate SQL insert statements, but we'd have to put all of the XML structures into memory first. Alternatively + /// we can use .net's DataTable, but this also requires putting everything into memory. By using a DataReader we don't have to + /// store every content item and it's XML structure in memory to get it into the DB, we can stream it into the db with this + /// reader. + /// + internal class PocoDataDataReader : BulkDataReader + where TSyntax : ISqlSyntaxProvider + { + private readonly MicrosoftSqlSyntaxProviderBase _sqlSyntaxProvider; + private readonly TableDefinition _tableDefinition; + private readonly PocoColumn[] _readerColumns; + private readonly IEnumerator _enumerator; + private readonly ColumnDefinition[] _columnDefinitions; + private int _recordsAffected = -1; + + public PocoDataDataReader( + IEnumerable dataSource, + PocoData pd, + MicrosoftSqlSyntaxProviderBase sqlSyntaxProvider) + { + if (dataSource == null) throw new ArgumentNullException(nameof(dataSource)); + if (sqlSyntaxProvider == null) throw new ArgumentNullException(nameof(sqlSyntaxProvider)); + + _tableDefinition = DefinitionFactory.GetTableDefinition(pd.Type, sqlSyntaxProvider); + if (_tableDefinition == null) throw new InvalidOperationException("No table definition found for type " + pd.Type); + + _readerColumns = pd.Columns.Select(x => x.Value).ToArray(); + _sqlSyntaxProvider = sqlSyntaxProvider; + _enumerator = dataSource.GetEnumerator(); + _columnDefinitions = _tableDefinition.Columns.ToArray(); + + } + + protected override string SchemaName => _tableDefinition.SchemaName; + + protected override string TableName => _tableDefinition.Name; + + public override int RecordsAffected => _recordsAffected <= 0 ? -1 : _recordsAffected; + + /// + /// This will automatically add the schema rows based on the Poco table definition and the columns passed in + /// + protected override void AddSchemaTableRows() + { + //var colNames = _readerColumns.Select(x => x.ColumnName).ToArray(); + //foreach (var col in _columnDefinitions.Where(x => colNames.Contains(x.Name, StringComparer.OrdinalIgnoreCase))) + foreach (var col in _columnDefinitions) + { + SqlDbType sqlDbType; + if (col.HasSpecialDbType) + { + //get the SqlDbType from the 'special type' + switch (col.DbType) + { + case SpecialDbTypes.NTEXT: + sqlDbType = SqlDbType.NText; + break; + case SpecialDbTypes.NCHAR: + sqlDbType = SqlDbType.NChar; + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + else if (col.Type.HasValue) + { + //get the SqlDbType from the DbType + sqlDbType = _sqlSyntaxProvider.GetSqlDbType(col.Type.Value); + } + else + { + //get the SqlDbType from the clr type + sqlDbType = _sqlSyntaxProvider.GetSqlDbType(col.PropertyType); + } + + AddSchemaTableRow( + col.Name, + col.Size > 0 ? (int?)col.Size : null, + col.Precision > 0 ? (short?)col.Precision : null, + null, col.IsUnique, col.IsIdentity, col.IsNullable, sqlDbType, + null, null, null, null, null); + } + } + + /// + /// Get the value from the column index for the current object + /// + /// + /// + public override object GetValue(int i) + { + return _enumerator.Current == null ? null : _readerColumns[i].GetValue(_enumerator.Current); + } + + /// + /// Advance the cursor + /// + /// + public override bool Read() + { + var result = _enumerator.MoveNext(); + if (result) + { + if (_recordsAffected == -1) + _recordsAffected = 0; + _recordsAffected++; + } + return result; + } + + /// + /// Ensure the enumerator is disposed + /// + /// + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + _enumerator.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs index bf837e87c5..3fb3d51609 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs @@ -145,7 +145,7 @@ namespace Umbraco.Core.Persistence.Repositories } } - _unitOfWork.Database.BulkInsertRecords(SqlSyntax, toInsert); + db.BulkInsertRecords(toInsert); //Raise the event AssignedPermissions.RaiseEvent( @@ -177,7 +177,7 @@ namespace Umbraco.Core.Persistence.Repositories UserId = userId }).ToArray(); - _unitOfWork.Database.BulkInsertRecords(SqlSyntax, actions); + db.BulkInsertRecords(actions); //Raise the event AssignedPermissions.RaiseEvent( @@ -209,7 +209,7 @@ namespace Umbraco.Core.Persistence.Repositories UserId = id }).ToArray(); - _unitOfWork.Database.BulkInsertRecords(SqlSyntax, actions); + db.BulkInsertRecords(actions); //Raise the event AssignedPermissions.RaiseEvent( @@ -237,7 +237,7 @@ namespace Umbraco.Core.Persistence.Repositories UserId = p.UserId }).ToArray(); - _unitOfWork.Database.BulkInsertRecords(SqlSyntax, actions); + db.BulkInsertRecords(actions); //Raise the event AssignedPermissions.RaiseEvent( diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs index 449f5fb3b1..1ea600b6e4 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs @@ -1,4 +1,6 @@ using System; +using System.Data; +using System.Linq; using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.SqlSyntax @@ -133,5 +135,111 @@ namespace Umbraco.Core.Persistence.SqlSyntax throw new ArgumentOutOfRangeException("columnType"); } } + + /// + /// This uses a the DbTypeMap created and custom mapping to resolve the SqlDbType + /// + /// + /// + public virtual SqlDbType GetSqlDbType(Type clrType) + { + var dbType = DbTypeMap.ColumnDbTypeMap.First(x => x.Key == clrType).Value; + return GetSqlDbType(dbType); + } + + /// + /// Returns the mapped SqlDbType for the DbType specified + /// + /// + /// + public virtual SqlDbType GetSqlDbType(DbType dbType) + { + var sqlDbType = SqlDbType.NVarChar; + + //SEE: https://msdn.microsoft.com/en-us/library/cc716729(v=vs.110).aspx + // and https://msdn.microsoft.com/en-us/library/yy6y35y8%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396 + switch (dbType) + { + case DbType.AnsiString: + sqlDbType = SqlDbType.VarChar; + break; + case DbType.Binary: + sqlDbType = SqlDbType.VarBinary; + break; + case DbType.Byte: + sqlDbType = SqlDbType.TinyInt; + break; + case DbType.Boolean: + sqlDbType = SqlDbType.Bit; + break; + case DbType.Currency: + sqlDbType = SqlDbType.Money; + break; + case DbType.Date: + sqlDbType = SqlDbType.Date; + break; + case DbType.DateTime: + sqlDbType = SqlDbType.DateTime; + break; + case DbType.Decimal: + sqlDbType = SqlDbType.Decimal; + break; + case DbType.Double: + sqlDbType = SqlDbType.Float; + break; + case DbType.Guid: + sqlDbType = SqlDbType.UniqueIdentifier; + break; + case DbType.Int16: + sqlDbType = SqlDbType.SmallInt; + break; + case DbType.Int32: + sqlDbType = SqlDbType.Int; + break; + case DbType.Int64: + sqlDbType = SqlDbType.BigInt; + break; + case DbType.Object: + sqlDbType = SqlDbType.Variant; + break; + case DbType.SByte: + throw new NotSupportedException("Inferring a SqlDbType from SByte is not supported."); + case DbType.Single: + sqlDbType = SqlDbType.Real; + break; + case DbType.String: + sqlDbType = SqlDbType.NVarChar; + break; + case DbType.Time: + sqlDbType = SqlDbType.Time; + break; + case DbType.UInt16: + throw new NotSupportedException("Inferring a SqlDbType from UInt16 is not supported."); + case DbType.UInt32: + throw new NotSupportedException("Inferring a SqlDbType from UInt32 is not supported."); + case DbType.UInt64: + throw new NotSupportedException("Inferring a SqlDbType from UInt64 is not supported."); + case DbType.VarNumeric: + throw new NotSupportedException("Inferring a VarNumeric from UInt64 is not supported."); + case DbType.AnsiStringFixedLength: + sqlDbType = SqlDbType.Char; + break; + case DbType.StringFixedLength: + sqlDbType = SqlDbType.NChar; + break; + case DbType.Xml: + sqlDbType = SqlDbType.Xml; + break; + case DbType.DateTime2: + sqlDbType = SqlDbType.DateTime2; + break; + case DbType.DateTimeOffset: + sqlDbType = SqlDbType.DateTimeOffset; + break; + default: + throw new ArgumentOutOfRangeException(); + } + return sqlDbType; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Properties/AssemblyInfo.cs b/src/Umbraco.Core/Properties/AssemblyInfo.cs index f631b66ae6..ceb3bf18f9 100644 --- a/src/Umbraco.Core/Properties/AssemblyInfo.cs +++ b/src/Umbraco.Core/Properties/AssemblyInfo.cs @@ -32,6 +32,7 @@ using System.Security.Permissions; [assembly: InternalsVisibleTo("umbraco.editorControls")] [assembly: InternalsVisibleTo("Umbraco.Tests")] +[assembly: InternalsVisibleTo("Umbraco.Tests.Benchmarks")] [assembly: InternalsVisibleTo("Umbraco.Core")] [assembly: InternalsVisibleTo("Umbraco.Web")] [assembly: InternalsVisibleTo("Umbraco.Web.UI")] diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs index 184e67d7c7..5fffadf713 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs @@ -30,7 +30,6 @@ namespace Umbraco.Core.Sync public class DatabaseServerMessenger : ServerMessengerBase { private readonly IRuntimeState _runtime; - private readonly DatabaseServerMessengerOptions _options; private readonly ManualResetEvent _syncIdle; private readonly object _locko = new object(); private readonly ProfilingLogger _profilingLogger; @@ -41,6 +40,8 @@ namespace Umbraco.Core.Sync private bool _syncing; private bool _released; + protected DatabaseServerMessengerOptions Options { get; private set; } + public DatabaseServerMessenger( IRuntimeState runtime, DatabaseContext dbContext, ILogger logger, ProfilingLogger proflog, bool distributedEnabled, DatabaseServerMessengerOptions options) @@ -55,7 +56,7 @@ namespace Umbraco.Core.Sync Logger = logger; _runtime = runtime; _profilingLogger = proflog; - _options = options; + Options = options; _lastPruned = _lastSync = DateTime.UtcNow; _syncIdle = new ManualResetEvent(true); } @@ -128,7 +129,17 @@ namespace Umbraco.Core.Sync { _released = true; // no more syncs } - _syncIdle.WaitOne(); // wait for pending sync + + // wait a max of 5 seconds and then return, so that we don't block + // the entire MainDom callbacks chain and prevent the AppDomain from + // properly releasing MainDom - a timeout here means that one refresher + // is taking too much time processing, however when it's done we will + // not update lastId and stop everything + var idle =_syncIdle.WaitOne(5000); + if (idle == false) + { + Logger.Warn("The wait lock timed out, application is shutting down. The current instruction batch will be re-processed."); + } }, weight); @@ -167,14 +178,16 @@ namespace Umbraco.Core.Sync else { //check for how many instructions there are to process + //TODO: In 7.6 we need to store the count of instructions per row since this is not affective because there can be far more than one (if not thousands) + // of instructions in a single row. var count = Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoCacheInstruction WHERE id > @lastId", new {lastId = _lastId}); - if (count > _options.MaxProcessingInstructionCount) + if (count > Options.MaxProcessingInstructionCount) { //too many instructions, proceed to cold boot Logger.Warn("The instruction count ({0}) exceeds the specified MaxProcessingInstructionCount ({1})." + " The server will skip existing instructions, rebuild its caches and indexes entirely, adjust its last synced Id" + " to the latest found in the database and maintain cache updates based on that Id.", - () => count, () => _options.MaxProcessingInstructionCount); + () => count, () => Options.MaxProcessingInstructionCount); coldboot = true; } @@ -192,8 +205,8 @@ namespace Umbraco.Core.Sync SaveLastSynced(maxId); // execute initializing callbacks - if (_options.InitializingCallbacks != null) - foreach (var callback in _options.InitializingCallbacks) + if (Options.InitializingCallbacks != null) + foreach (var callback in Options.InitializingCallbacks) callback(); } @@ -211,12 +224,14 @@ namespace Umbraco.Core.Sync if (_syncing) return; + //Don't continue if we are released if (_released) return; - if ((DateTime.UtcNow - _lastSync).TotalSeconds <= _options.ThrottleSeconds) + if ((DateTime.UtcNow - _lastSync).TotalSeconds <= Options.ThrottleSeconds) return; + //Set our flag and the lock to be in it's original state (i.e. it can be awaited) _syncing = true; _syncIdle.Reset(); _lastSync = DateTime.UtcNow; @@ -228,7 +243,8 @@ namespace Umbraco.Core.Sync { ProcessDatabaseInstructions(); - if ((DateTime.UtcNow - _lastPruned).TotalSeconds <= _options.PruneThrottleSeconds) + //Check for pruning throttling + if ((_released || (DateTime.UtcNow - _lastPruned).TotalSeconds <= Options.PruneThrottleSeconds)) return; _lastPruned = _lastSync; @@ -244,7 +260,12 @@ namespace Umbraco.Core.Sync } finally { - _syncing = false; + lock (_locko) + { + //We must reset our flag and signal any waiting locks + _syncing = false; + } + _syncIdle.Set(); } } @@ -268,13 +289,17 @@ namespace Umbraco.Core.Sync // // FIXME not true if we're running on a background thread, assuming we can? + var sql = Sql().SelectAll() .From() .Where(dto => dto.Id > _lastId) .OrderBy(dto => dto.Id); - var dtos = Database.Fetch(sql); - if (dtos.Count <= 0) return; + //only retrieve the top 100 (just in case there's tons) + // even though MaxProcessingInstructionCount is by default 1000 we still don't want to process that many + // rows in one request thread since each row can contain a ton of instructions (until 7.5.5 in which case + // a row can only contain MaxProcessingInstructionCount) + var topSql = sql.SelectTop(100); // only process instructions coming from a remote server, and ignore instructions coming from // the local server as they've already been processed. We should NOT assume that the sequence of @@ -282,8 +307,22 @@ namespace Umbraco.Core.Sync var localIdentity = LocalIdentity; var lastId = 0; - foreach (var dto in dtos) + + //tracks which ones have already been processed to avoid duplicates + var processed = new HashSet(); + + //It would have been nice to do this in a Query instead of Fetch using a data reader to save + // some memory however we cannot do thta because inside of this loop the cache refreshers are also + // performing some lookups which cannot be done with an active reader open + foreach (var dto in Database.Fetch(topSql)) { + //If this flag gets set it means we're shutting down! In this case, we need to exit asap and cannot + // continue processing anything otherwise we'll hold up the app domain shutdown + if (_released) + { + break; + } + if (dto.OriginIdentity == localIdentity) { // just skip that local one but update lastId nevertheless @@ -304,27 +343,69 @@ namespace Umbraco.Core.Sync continue; } - // execute remote instructions & update lastId - try - { - NotifyRefreshers(jsonA); - lastId = dto.Id; - } - catch (Exception ex) - { - Logger.Error( - $"DISTRIBUTED CACHE IS NOT UPDATED. Failed to execute instructions ({dto.Id}: \"{dto.Instructions}\"). Instruction is being skipped/ignored", ex); + var instructionBatch = GetAllInstructions(jsonA); - //we cannot throw here because this invalid instruction will just keep getting processed over and over and errors - // will be thrown over and over. The only thing we can do is ignore and move on. - lastId = dto.Id; + //process as per-normal + var success = ProcessDatabaseInstructions(instructionBatch, dto, processed, ref lastId); + + //if they couldn't be all processed (i.e. we're shutting down) then exit + if (success == false) + { + Logger.Info("The current batch of instructions was not processed, app is shutting down"); + break; } + } if (lastId > 0) SaveLastSynced(lastId); } + /// + /// Processes the instruction batch and checks for errors + /// + /// + /// + /// + /// Tracks which instructions have already been processed to avoid duplicates + /// + /// + /// + /// returns true if all instructions in the batch were processed, otherwise false if they could not be due to the app being shut down + /// + private bool ProcessDatabaseInstructions(IReadOnlyCollection instructionBatch, CacheInstructionDto dto, HashSet processed, ref int lastId) + { + // execute remote instructions & update lastId + try + { + var result = NotifyRefreshers(instructionBatch, processed); + if (result) + { + //if all instructions we're processed, set the last id + lastId = dto.Id; + } + return result; + } + //catch (ThreadAbortException ex) + //{ + // //This will occur if the instructions processing is taking too long since this is occuring on a request thread. + // // Or possibly if IIS terminates the appdomain. In any case, we should deal with this differently perhaps... + //} + catch (Exception ex) + { + Logger.Error( + $"DISTRIBUTED CACHE IS NOT UPDATED. Failed to execute instructions ({dto.Id}: \"{dto.Instructions}\"). Instruction is being skipped/ignored", ex); + + //we cannot throw here because this invalid instruction will just keep getting processed over and over and errors + // will be thrown over and over. The only thing we can do is ignore and move on. + lastId = dto.Id; + return false; + } + + ////if this is returned it will not be saved + //return -1; + } + /// /// Remove old instructions from the database /// @@ -335,7 +416,7 @@ namespace Umbraco.Core.Sync /// private void PruneOldInstructions() { - var pruneDate = DateTime.UtcNow.AddDays(-_options.DaysToRetainInstructions); + var pruneDate = DateTime.UtcNow.AddDays(-Options.DaysToRetainInstructions); // using 2 queries is faster than convoluted joins @@ -472,8 +553,14 @@ namespace Umbraco.Core.Sync return jsonRefresher; } - private static void NotifyRefreshers(IEnumerable jsonArray) + /// + /// Parses out the individual instructions to be processed + /// + /// + /// + private static List GetAllInstructions(IEnumerable jsonArray) { + var result = new List(); foreach (var jsonItem in jsonArray) { // could be a JObject in which case we can convert to a RefreshInstruction, @@ -482,35 +569,64 @@ namespace Umbraco.Core.Sync if (jsonObj != null) { var instruction = jsonObj.ToObject(); - switch (instruction.RefreshType) - { - case RefreshMethodType.RefreshAll: - RefreshAll(instruction.RefresherId); - break; - case RefreshMethodType.RefreshByGuid: - RefreshByGuid(instruction.RefresherId, instruction.GuidId); - break; - case RefreshMethodType.RefreshById: - RefreshById(instruction.RefresherId, instruction.IntId); - break; - case RefreshMethodType.RefreshByIds: - RefreshByIds(instruction.RefresherId, instruction.JsonIds); - break; - case RefreshMethodType.RefreshByJson: - RefreshByJson(instruction.RefresherId, instruction.JsonPayload); - break; - case RefreshMethodType.RemoveById: - RemoveById(instruction.RefresherId, instruction.IntId); - break; - } - + result.Add(instruction); } else { - var jsonInnerArray = (JArray) jsonItem; - NotifyRefreshers(jsonInnerArray); // recurse + var jsonInnerArray = (JArray)jsonItem; + result.AddRange(GetAllInstructions(jsonInnerArray)); // recurse } } + return result; + } + + /// + /// executes the instructions against the cache refresher instances + /// + /// + /// + /// + /// Returns true if all instructions were processed, otherwise false if the processing was interupted (i.e. app shutdown) + /// + private bool NotifyRefreshers(IEnumerable instructions, HashSet processed) + { + foreach (var instruction in instructions) + { + //Check if the app is shutting down, we need to exit if this happens. + if (_released) + { + return false; + } + + //this has already been processed + if (processed.Contains(instruction)) + continue; + + switch (instruction.RefreshType) + { + case RefreshMethodType.RefreshAll: + RefreshAll(instruction.RefresherId); + break; + case RefreshMethodType.RefreshByGuid: + RefreshByGuid(instruction.RefresherId, instruction.GuidId); + break; + case RefreshMethodType.RefreshById: + RefreshById(instruction.RefresherId, instruction.IntId); + break; + case RefreshMethodType.RefreshByIds: + RefreshByIds(instruction.RefresherId, instruction.JsonIds); + break; + case RefreshMethodType.RefreshByJson: + RefreshByJson(instruction.RefresherId, instruction.JsonPayload); + break; + case RefreshMethodType.RemoveById: + RemoveById(instruction.RefresherId, instruction.IntId); + break; + } + + processed.Add(instruction); + } + return true; } private static void RefreshAll(Guid uniqueIdentifier) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index f2d15bd720..85d09e3b0c 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -288,7 +288,6 @@ - @@ -306,6 +305,7 @@ + @@ -324,6 +324,7 @@ + @@ -384,6 +385,7 @@ + @@ -439,6 +441,7 @@ + diff --git a/src/Umbraco.Tests.Benchmarks/App.config b/src/Umbraco.Tests.Benchmarks/App.config new file mode 100644 index 0000000000..7352927e47 --- /dev/null +++ b/src/Umbraco.Tests.Benchmarks/App.config @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Tests.Benchmarks/BulkInsertBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/BulkInsertBenchmarks.cs new file mode 100644 index 0000000000..1a59e54a02 --- /dev/null +++ b/src/Umbraco.Tests.Benchmarks/BulkInsertBenchmarks.cs @@ -0,0 +1,218 @@ +using System; +using System.Collections.Generic; +using System.Data.SqlServerCe; +using System.IO; +using System.Linq; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnostics.Windows; +using BenchmarkDotNet.Jobs; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Persistence.Migrations.Initial; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Tests.TestHelpers; +using ILogger = Umbraco.Core.Logging.ILogger; + +namespace Umbraco.Tests.Benchmarks +{ + [Config(typeof(Config))] + public class BulkInsertBenchmarks + { + private class Config : ManualConfig + { + public Config() + { + Add(new MemoryDiagnoser()); + //Add(ExecutionValidator.FailOnError); + + //The 'quick and dirty' settings, so it runs a little quicker + // see benchmarkdotnet FAQ + Add(Job.Default + .WithLaunchCount(1) // benchmark process will be launched only once + .WithIterationTime(100) // 100ms per iteration + .WithWarmupCount(3) // 3 warmup iteration + .WithTargetCount(3)); // 3 target iteration + } + } + + private static byte[] _initDbBytes; + + // fixme - should run on LocalDb same as NPoco tests! + + private UmbracoDatabase GetSqlServerDatabase(ILogger logger) + { + IDatabaseFactory f = null; + var l = new Lazy(() => f); + f = new DefaultDatabaseFactory( + "server=.\\SQLExpress;database=YOURDB;user id=YOURUSER;password=YOURPASS", + Constants.DatabaseProviders.SqlServer, + new [] { new SqlServerSyntaxProvider(l) }, + logger, + new ThreadStaticUmbracoDatabaseAccessor(), + new MapperCollection(Enumerable.Empty())); + return f.GetDatabase(); + } + + private UmbracoDatabase GetSqlCeDatabase(string cstr, ILogger logger) + { + var f = new DefaultDatabaseFactory( + cstr, + Constants.DatabaseProviders.SqlCe, + new[] { new SqlCeSyntaxProvider() }, + logger, + new ThreadStaticUmbracoDatabaseAccessor(), + new MapperCollection(Enumerable.Empty())); + return f.GetDatabase(); + } + + [Setup] + public void Setup() + { + var logger = new DebugDiagnosticsLogger(); + var path = TestHelper.CurrentAssemblyDirectory; + + SetupSqlCe(path, logger); + SetupSqlServer(logger); + + + } + + private void SetupSqlServer(ILogger logger) + { + //create the db + _dbSqlServer = GetSqlServerDatabase(logger); + + //drop the table + // note: DROP TABLE IF EXISTS is SQL 2016+ + _dbSqlServer.Execute("IF OBJECT_ID('dbo.umbracoServer', 'U') IS NOT NULL DROP TABLE [umbracoServer]"); + + //re-create it + _dbSqlServer.Execute(@"CREATE TABLE [umbracoServer]( + [id] [int] IDENTITY(1,1) NOT NULL, + [address] [nvarchar](500) NOT NULL, + [computerName] [nvarchar](255) NOT NULL, + [registeredDate] [datetime] NOT NULL CONSTRAINT [DF_umbracoServer_registeredDate] DEFAULT (getdate()), + [lastNotifiedDate] [datetime] NOT NULL, + [isActive] [bit] NOT NULL, + [isMaster] [bit] NOT NULL, + CONSTRAINT [PK_umbracoServer] PRIMARY KEY CLUSTERED +( + [id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] +)"); + } + + private void SetupSqlCe(string path, ILogger logger) + { + var dbName = string.Concat("Umb", Guid.NewGuid(), ".sdf"); + AppDomain.CurrentDomain.SetData("DataDirectory", path); + var sqlCeConnectionString = $"Datasource=|DataDirectory|\\{dbName};Flush Interval=1;"; + + _dbFile = Path.Combine(path, dbName); + + //only create the db one time + if (_initDbBytes == null) + { + using (var engine = new SqlCeEngine(sqlCeConnectionString)) + { + engine.CreateDatabase(); + } + + //use the db to create the initial schema so we can reuse in each bench + using (_dbSqlCe = GetSqlCeDatabase(sqlCeConnectionString, logger)) + { + var creation = new DatabaseSchemaCreation(_dbSqlCe, logger); + creation.InitializeDatabaseSchema(); + } + _initDbBytes = File.ReadAllBytes(_dbFile); + } + else + { + File.WriteAllBytes(_dbFile, _initDbBytes); + } + + //create the db + _dbSqlCe = GetSqlCeDatabase(sqlCeConnectionString, logger); + } + + private List GetData() + { + var data = new List(); + for (var i = 0; i < 1000; i++) + { + data.Add(new ServerRegistrationDto + { + ServerAddress = "address" + Guid.NewGuid(), + ServerIdentity = "computer" + Guid.NewGuid(), + DateRegistered = DateTime.Now, + IsActive = true, + DateAccessed = DateTime.Now + }); + } + return data; + } + + [Cleanup] + public void Cleanup() + { + _dbSqlCe.Dispose(); + _dbSqlServer.Dispose(); + File.Delete(_dbFile); + } + + private string _dbFile; + private UmbracoDatabase _dbSqlCe; + private UmbracoDatabase _dbSqlServer; + + /// + /// Tests updating the existing XML way + /// + [Benchmark(Baseline = true)] + public void SqlCeOneByOne() + { + using (var tr = _dbSqlCe.GetTransaction()) + { + _dbSqlCe.BulkInsertRecords(GetData(), false); + tr.Complete(); + } + } + + /// + /// Tests updating with only the object graph + /// + [Benchmark] + public void SqlCeTableDirect() + { + using (var tr = _dbSqlCe.GetTransaction()) + { + _dbSqlCe.BulkInsertRecords(GetData()); + tr.Complete(); + } + } + + [Benchmark] + public void SqlServerBulkInsertStatements() + { + using (var tr = _dbSqlServer.GetTransaction()) + { + _dbSqlServer.BulkInsertRecords(GetData(), false); + tr.Complete(); + } + } + + [Benchmark] + public void SqlServerBulkCopy() + { + using (var tr = _dbSqlServer.GetTransaction()) + { + _dbSqlServer.BulkInsertRecords(GetData()); + tr.Complete(); + } + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests.Benchmarks/Program.cs b/src/Umbraco.Tests.Benchmarks/Program.cs new file mode 100644 index 0000000000..91ae1d37c3 --- /dev/null +++ b/src/Umbraco.Tests.Benchmarks/Program.cs @@ -0,0 +1,35 @@ +using System; +using System.Reflection; +using BenchmarkDotNet.Running; + +namespace Umbraco.Tests.Benchmarks +{ + internal class Program + { + public static void Main(string[] args) + { + if (args.Length == 0) + { + var summary = BenchmarkRunner.Run(); + Console.ReadLine(); + } + else if (args.Length == 1) + { + var type = Assembly.GetExecutingAssembly().GetType("Umbraco.Tests.Benchmarks." +args[0]); + if (type == null) + { + Console.WriteLine("Unknown benchmark."); + } + else + { + var summary = BenchmarkRunner.Run(type); + Console.ReadLine(); + } + } + else + { + Console.WriteLine("?"); + } + } + } +} diff --git a/src/Umbraco.Tests.Benchmarks/Properties/AssemblyInfo.cs b/src/Umbraco.Tests.Benchmarks/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..fb00b8d4e9 --- /dev/null +++ b/src/Umbraco.Tests.Benchmarks/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("Umbraco.Tests.Benchmarks")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Umbraco.Tests.Benchmarks")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[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("86deb346-089f-4106-89c8-d852b9cf2a33")] + +// 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/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj new file mode 100644 index 0000000000..94b7834f60 --- /dev/null +++ b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj @@ -0,0 +1,153 @@ + + + + + Debug + AnyCPU + {86DEB346-089F-4106-89C8-D852B9CF2A33} + Exe + Properties + Umbraco.Tests.Benchmarks + Umbraco.Tests.Benchmarks + v4.6.1 + 512 + + + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + Always + + + + ..\packages\BenchmarkDotNet.0.9.9\lib\net45\BenchmarkDotNet.dll + True + + + ..\packages\BenchmarkDotNet.Core.0.9.9\lib\net45\BenchmarkDotNet.Core.dll + True + + + ..\packages\BenchmarkDotNet.Diagnostics.Windows.0.9.9\lib\net45\BenchmarkDotNet.Diagnostics.Windows.dll + True + + + ..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.9.9\lib\net45\BenchmarkDotNet.Toolchains.Roslyn.dll + True + + + ..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.dll + True + + + ..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\net45\Microsoft.CodeAnalysis.CSharp.dll + True + + + ..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll + True + + + ..\packages\NPoco.3.4.5\lib\net45\NPoco.dll + True + + + + ..\packages\System.Collections.Immutable.1.1.37\lib\dotnet\System.Collections.Immutable.dll + True + + + + + ..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.dll + True + + + ..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.Entity.dll + True + + + + ..\packages\System.Reflection.Metadata.1.2.0\lib\portable-net45+win8\System.Reflection.Metadata.dll + True + + + + + ..\packages\System.Threading.Tasks.Extensions.4.0.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + {31785bc3-256c-4613-b2f5-a1b0bdded8c1} + Umbraco.Core + + + {5d3b8245-ada6-453f-a008-50ed04bfe770} + Umbraco.Tests + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + + xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\amd64\*.* "$(TargetDir)amd64\" /Y /F /E /I /C /D +xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\x86\*.* "$(TargetDir)x86\" /Y /F /E /I /C /D + + + \ No newline at end of file diff --git a/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj.DotSettings b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj.DotSettings new file mode 100644 index 0000000000..73e96563f9 --- /dev/null +++ b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp60 \ No newline at end of file diff --git a/src/Umbraco.Tests.Benchmarks/XmlBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/XmlBenchmarks.cs new file mode 100644 index 0000000000..c12545ec94 --- /dev/null +++ b/src/Umbraco.Tests.Benchmarks/XmlBenchmarks.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnostics.Windows; +using BenchmarkDotNet.Jobs; + +namespace Umbraco.Tests.Benchmarks +{ + [Config(typeof(Config))] + public class XmlBenchmarks + { + private class Config : ManualConfig + { + public Config() + { + Add(new MemoryDiagnoser()); + //Add(ExecutionValidator.FailOnError); + + //The 'quick and dirty' settings, so it runs a little quicker + // see benchmarkdotnet FAQ + Add(Job.Default + .WithLaunchCount(1) // benchmark process will be launched only once + .WithIterationTime(100) // 100ms per iteration + .WithWarmupCount(3) // 3 warmup iteration + .WithTargetCount(3)); // 3 target iteration + } + } + + [Setup] + public void Setup() + { + var templateId = 0; + var xmlText = @" + + + + +]> + + + + + 1 + + This is some content]]> + + + + + + + + + + + + + + + + +"; + _xml = new XmlDocument(); + _xml.LoadXml(xmlText); + } + + [Cleanup] + public void Cleanup() + { + _xml = null; + } + + private XmlDocument _xml; + + [Benchmark] + public void XmlWithXPath() + { + var xpath = "/root/* [@isDoc and @urlName='home']//* [@isDoc and @urlName='sub1']//* [@isDoc and @urlName='sub2']"; + var elt = _xml.SelectSingleNode(xpath); + if (elt == null) Console.WriteLine("ERR"); + } + + [Benchmark] + public void XmlWithNavigation() + { + var elt = _xml.DocumentElement; + var id = NavigateElementRoute(elt, new[] {"home", "sub1", "sub2"}); + if (id <= 0) Console.WriteLine("ERR"); + } + + private const bool UseLegacySchema = false; + + private int NavigateElementRoute(XmlElement elt, string[] urlParts) + { + var found = true; + var i = 0; + while (found && i < urlParts.Length) + { + found = false; + foreach (XmlElement child in elt.ChildNodes) + { + var noNode = UseLegacySchema + ? child.Name != "node" + : child.GetAttributeNode("isDoc") == null; + if (noNode) continue; + if (child.GetAttribute("urlName") != urlParts[i]) continue; + + found = true; + elt = child; + break; + } + i++; + } + return found ? int.Parse(elt.GetAttribute("id")) : -1; + } + } +} diff --git a/src/Umbraco.Tests.Benchmarks/packages.config b/src/Umbraco.Tests.Benchmarks/packages.config new file mode 100644 index 0000000000..76eebe173a --- /dev/null +++ b/src/Umbraco.Tests.Benchmarks/packages.config @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs index 4a6a1fe292..080d3e87ef 100644 --- a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs +++ b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs @@ -15,6 +15,11 @@ namespace Umbraco.Tests.IO [TestFixture] public class ShadowFileSystemTests { + // tested: + // only 1 instance of this class is created + // SetUp and TearDown run before/after each test + // SetUp does not start before the previous TearDown returns + [SetUp] public void SetUp() { diff --git a/src/Umbraco.Tests/Persistence/BulkDataReaderTests.cs b/src/Umbraco.Tests/Persistence/BulkDataReaderTests.cs new file mode 100644 index 0000000000..b1e1a79ddb --- /dev/null +++ b/src/Umbraco.Tests/Persistence/BulkDataReaderTests.cs @@ -0,0 +1,2432 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Data; +using System.Data.Common; +using System.Data.SqlClient; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; +using Umbraco.Core.Persistence; + +namespace Umbraco.Tests.Persistence +{ + /// + /// Unit tests for . + /// + /// + /// Borrowed from Microsoft: + /// See: https://blogs.msdn.microsoft.com/anthonybloesch/2013/01/23/bulk-loading-data-with-idatareader-and-sqlbulkcopy/ + /// + [TestFixture] + public class BulkDataReaderTest + { + + #region Test constants + + /// + /// The schema name. + /// + private const string testSchemaName = "TestSchema"; + + /// + /// The table name. + /// + private const string testTableName = "TestTable"; + + /// + /// The test UDT schema name. + /// + private const string testUdtSchemaName = "UdtSchema"; + + /// + /// The test UDT name. + /// + private const string testUdtName = "TestUdt"; + + /// + /// The test XML schema collection database name. + /// + private const string testXmlSchemaCollectionDatabaseName = "XmlDatabase"; + + /// + /// The test XML schema collection owning schema name. + /// + private const string testXMLSchemaCollectionSchemaName = "XmlSchema"; + + /// + /// The test XML schema collection name. + /// + private const string testXMLSchemaCollectionName = "Xml"; + + #endregion + + #region Schema tests + + /// + /// Test that is functioning correctly. + /// + /// + [Test] + public void ColumnMappingsTest() + { + using (BulkDataReaderSubclass testReader = new BulkDataReaderSubclass()) + { + ReadOnlyCollection columnMappings = testReader.ColumnMappings; + + Assert.IsTrue(columnMappings.Count > 0); + Assert.AreEqual(columnMappings.Count, testReader.FieldCount); + + foreach (SqlBulkCopyColumnMapping columnMapping in columnMappings) + { + Assert.AreEqual(columnMapping.SourceColumn, columnMapping.DestinationColumn); + } + } + } + + /// + /// Test that is functioning correctly. + /// + /// + [Test] + public void GetDataTypeNameTest() + { + using (BulkDataReaderSubclass testReader = new BulkDataReaderSubclass()) + { + Assert.IsTrue(testReader.FieldCount > 0); + + for (int currentColumn = 0; currentColumn < testReader.FieldCount; currentColumn++) + { + Assert.AreEqual(testReader.GetDataTypeName(currentColumn), ((Type)testReader.GetSchemaTable().Rows[currentColumn][SchemaTableColumn.DataType]).Name); + } + } + } + + /// + /// Test that is functioning correctly. + /// + /// + [Test] + public void GetFieldTypeTest() + { + using (BulkDataReaderSubclass testReader = new BulkDataReaderSubclass()) + { + Assert.IsTrue(testReader.FieldCount > 0); + + for (int currentColumn = 0; currentColumn < testReader.FieldCount; currentColumn++) + { + Assert.AreEqual(testReader.GetFieldType(currentColumn), testReader.GetSchemaTable().Rows[currentColumn][SchemaTableColumn.DataType]); + } + } + } + + /// + /// Test that is functioning correctly. + /// + /// + [Test] + public void GetOrdinalTest() + { + using (BulkDataReaderSubclass testReader = new BulkDataReaderSubclass()) + { + Assert.IsTrue(testReader.FieldCount > 0); + + for (int currentColumn = 0; currentColumn < testReader.FieldCount; currentColumn++) + { + Assert.AreEqual(testReader.GetOrdinal(testReader.GetName(currentColumn)), currentColumn); + + Assert.AreEqual(testReader.GetOrdinal(testReader.GetName(currentColumn).ToUpperInvariant()), currentColumn); + } + } + } + + /// + /// Test that functions correctly. + /// + /// + /// uses to test legal schema combinations. + /// + /// + [Test] + public void GetSchemaTableTest() + { + using (BulkDataReaderSubclass testReader = new BulkDataReaderSubclass()) + { + DataTable schemaTable = testReader.GetSchemaTable(); + + Assert.IsNotNull(schemaTable); + Assert.IsTrue(schemaTable.Rows.Count > 0); + Assert.AreEqual(schemaTable.Rows.Count, BulkDataReaderSubclass.ExpectedResultSet.Count); + } + } + + /// + /// Test that + /// throws a for null column names. + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentException))] + public void AddSchemaTableRowNullColumnNameTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = null; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.BigInt; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for empty column names. + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentException))] + public void AddSchemaTableRowEmptyColumnNameTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = string.Empty; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.BigInt; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for nonpositive column sizes. + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void AddSchemaTableRowNonpositiveColumnSizeTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = 0; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.NVarChar; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for nonpositive numeric precision. + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void AddSchemaTableRowNonpositiveNumericPrecisionTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = 0; + testReader.NumericScale = 0; + testReader.ProviderType = SqlDbType.Decimal; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for negative numeric scale. + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void AddSchemaTableRowNegativeNumericScaleTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = 5; + testReader.NumericScale = -1; + testReader.ProviderType = SqlDbType.Decimal; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for binary column without a column size. + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentException))] + public void AddSchemaTableRowBinaryWithoutSizeTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.Binary; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for binary column with a column size that is too large (>8000). + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void AddSchemaTableRowBinaryWithTooLargeSizeTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = 8001; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.Binary; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for char column without a column size. + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentException))] + public void AddSchemaTableRowCharWithoutSizeTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.Char; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for char column with a column size that is too large (>8000). + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void AddSchemaTableRowCharWithTooLargeSizeTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = 8001; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.Char; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for decimal column without a column precision. + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentException))] + public void AddSchemaTableRowDecimalWithoutPrecisionTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = 5; + testReader.ProviderType = SqlDbType.Decimal; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for decimal column without a column scale. + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentException))] + public void AddSchemaTableRowDecimalWithoutScaleTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = 20; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.Decimal; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for decimal column with a column precision that is too large (>38). + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void AddSchemaTableRowDecimalWithTooLargePrecisionTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = 39; + testReader.NumericScale = 5; + testReader.ProviderType = SqlDbType.Decimal; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for decimal column with a column scale that is larger than the column precision. + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentException))] + public void AddSchemaTableRowDecimalWithTooLargeScaleTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = 20; + testReader.NumericScale = 21; + testReader.ProviderType = SqlDbType.Decimal; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for datetime2 column with a column size that has a precision that is too large (>7). + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void AddSchemaTableRowDateTime2WithTooLargePrecisionTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = 8; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.DateTime2; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for datetimeoffset column with a column size that has a precision that is too large (>7). + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void AddSchemaTableRowDateTimeOffsetWithTooLargePrecisionTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = 8; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.DateTimeOffset; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for nchar column without a precision. + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentException))] + public void AddSchemaTableRowFloatWithoutPrecisionTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.Float; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for float column with a column precision that is too large (>53). + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void AddSchemaTableRowFloatWithTooLargePrecisionTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = 54; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.Float; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for nchar column without a column size. + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentException))] + public void AddSchemaTableRowNCharWithoutSizeTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.NChar; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for nchar column with a column size that is too large (>4000). + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void AddSchemaTableRowNCharWithTooLargeSizeTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = 4001; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.NChar; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for nvarchar column with a column size that is too large (>4000). + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void AddSchemaTableRowNVarCharWithTooLargeSizeTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = 4001; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.NVarChar; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for time column with a column precision that is too large (>7). + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void AddSchemaTableRowTimeWithTooLargePrecisionTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = 8; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.Time; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for missing UDT schema name. + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentException))] + public void AddSchemaTableRowUdtMissingSchemaNameTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.Udt; + testReader.UdtSchema = null; + testReader.UdtType = "Type"; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for empty UDT schema name. + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentException))] + public void AddSchemaTableRowUdtEmptySchemaNameTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.Udt; + testReader.UdtSchema = string.Empty; + testReader.UdtType = "Type"; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for missing UDT name. + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentException))] + public void AddSchemaTableRowUdtMissingNameTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.Udt; + testReader.UdtSchema = "Schema"; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for empty UDT name. + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentException))] + public void AddSchemaTableRowUdtEmptyNameTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.Udt; + testReader.UdtSchema = "Schema"; + testReader.UdtType = string.Empty; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for varbinary column with a column size that is too large (>8000). + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void AddSchemaTableRowVarBinaryWithTooLargeSizeTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = 8001; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.VarBinary; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for varchar column with a column size that is too large (>8000). + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void AddSchemaTableRowVarCharWithTooLargeSizeTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = 8001; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.VarChar; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for null xml collection name but with a name for the database. + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentException))] + public void AddSchemaTableRowXmlNullNameWithDatabaseNameTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.Xml; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = "Database"; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for null xml collection name. + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentException))] + public void AddSchemaTableRowXmlNullNameWithOwningSchemaNameTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.Xml; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = "Schema"; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for empty xml collection database name. + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentException))] + public void AddSchemaTableRowXmlEmptyDatabaseNameTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.Xml; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = string.Empty; + testReader.XmlSchemaCollectionOwningSchema = "Schema"; + testReader.XmlSchemaCollectionName = "Xml"; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for empty xml collection owning schema name. + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentException))] + public void AddSchemaTableRowXmlEmptyOwningSchemaNameTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.Xml; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = "Database"; + testReader.XmlSchemaCollectionOwningSchema = string.Empty; + testReader.XmlSchemaCollectionName = "Xml"; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for empty xml collection name. + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentException))] + public void AddSchemaTableRowXmlEmptyNameTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.Xml; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = "Database"; + testReader.XmlSchemaCollectionOwningSchema = "Schema"; + testReader.XmlSchemaCollectionName = string.Empty; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for a structured column (which is illegal). + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void AddSchemaTableRowStructuredTypeTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.Structured; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws a for a timestamp column (which is illegal). + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void AddSchemaTableRowTimestampTypeTest() + { + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.ProviderType = SqlDbType.Timestamp; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + DataTable schemaTable = testReader.GetSchemaTable(); ; + } + } + + /// + /// Test that + /// throws an for a column with an unallowed optional column set. + /// + /// + /// Uses to test the illegal schema combination. + /// + /// + [Test] + public void AddSchemaTableRowUnallowedOptionalColumnTest() + { + + // Column size set + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = 5; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + foreach (SqlDbType dbtype in new List { SqlDbType.BigInt, SqlDbType.Bit, SqlDbType.Date, SqlDbType.DateTime, SqlDbType.DateTime2, + SqlDbType.DateTimeOffset, SqlDbType.Image, SqlDbType.Int, SqlDbType.Money, SqlDbType.Real, + SqlDbType.SmallDateTime, SqlDbType.SmallInt, SqlDbType.SmallMoney, SqlDbType.Structured, SqlDbType.Text, + SqlDbType.Time, SqlDbType.Timestamp, SqlDbType.TinyInt, SqlDbType.Udt, SqlDbType.UniqueIdentifier, + SqlDbType.Variant, SqlDbType.Xml }) + { + testReader.ProviderType = dbtype; + + try + { + DataTable schemaTable = testReader.GetSchemaTable(); + + Assert.Fail(); + } + catch (ArgumentException) + { + } + } + } + + // Numeric precision set + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = 5; + testReader.NumericScale = null; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + foreach (SqlDbType dbtype in new List { SqlDbType.BigInt, SqlDbType.Binary, SqlDbType.Bit, SqlDbType.Char, SqlDbType.Date, + SqlDbType.DateTime, SqlDbType.Image, SqlDbType.Int, SqlDbType.Money, SqlDbType.NChar, + SqlDbType.NText, SqlDbType.NVarChar, SqlDbType.Real, SqlDbType.SmallDateTime, SqlDbType.SmallInt, + SqlDbType.SmallMoney, SqlDbType.Structured, SqlDbType.Text, SqlDbType.Timestamp, SqlDbType.TinyInt, + SqlDbType.Udt, SqlDbType.UniqueIdentifier, SqlDbType.VarBinary, SqlDbType.VarChar, SqlDbType.Variant, + SqlDbType.Xml }) + { + testReader.ProviderType = dbtype; + + try + { + DataTable schemaTable = testReader.GetSchemaTable(); + + Assert.Fail(); + } + catch (ArgumentException) + { + } + } + } + + // Numeric scale set + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = 5; + testReader.NumericScale = 3; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + foreach (SqlDbType dbtype in new List { SqlDbType.BigInt, SqlDbType.Binary, SqlDbType.Bit, SqlDbType.Char, SqlDbType.Date, + SqlDbType.DateTime, SqlDbType.DateTime2, SqlDbType.DateTimeOffset, SqlDbType.Image, SqlDbType.Int, + SqlDbType.Money, SqlDbType.NChar, SqlDbType.NText, SqlDbType.NVarChar, SqlDbType.Real, + SqlDbType.SmallDateTime, SqlDbType.SmallInt, SqlDbType.SmallMoney, SqlDbType.Structured, SqlDbType.Text, + SqlDbType.Time, SqlDbType.Timestamp, SqlDbType.TinyInt, SqlDbType.Udt, SqlDbType.UniqueIdentifier, + SqlDbType.VarBinary, SqlDbType.VarChar, SqlDbType.Variant, SqlDbType.Xml }) + { + testReader.ProviderType = dbtype; + + try + { + DataTable schemaTable = testReader.GetSchemaTable(); + + Assert.Fail(); + } + catch (ArgumentException) + { + } + } + } + + // Numeric scale set + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = 5; + testReader.NumericScale = 3; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + foreach (SqlDbType dbtype in new List { SqlDbType.BigInt, SqlDbType.Binary, SqlDbType.Bit, SqlDbType.Char, SqlDbType.Date, + SqlDbType.DateTime, SqlDbType.DateTime2, SqlDbType.DateTimeOffset, SqlDbType.Image, SqlDbType.Int, + SqlDbType.Money, SqlDbType.NChar, SqlDbType.NText, SqlDbType.NVarChar, SqlDbType.Real, + SqlDbType.SmallDateTime, SqlDbType.SmallInt, SqlDbType.SmallMoney, SqlDbType.Structured, SqlDbType.Text, + SqlDbType.Time, SqlDbType.Timestamp, SqlDbType.TinyInt, SqlDbType.Udt, SqlDbType.UniqueIdentifier, + SqlDbType.VarBinary, SqlDbType.VarChar, SqlDbType.Variant, SqlDbType.Xml }) + { + testReader.ProviderType = dbtype; + + try + { + DataTable schemaTable = testReader.GetSchemaTable(); + + Assert.Fail(); + } + catch (ArgumentException) + { + } + } + } + + // UDT type name set + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.UdtSchema = null; + testReader.UdtType = "Type"; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + foreach (SqlDbType dbtype in new List { SqlDbType.BigInt, SqlDbType.Binary, SqlDbType.Bit, SqlDbType.Char, SqlDbType.Date, + SqlDbType.DateTime, SqlDbType.DateTime2, SqlDbType.DateTimeOffset, SqlDbType.Decimal, SqlDbType.Float, + SqlDbType.Image, SqlDbType.Int, SqlDbType.Money, SqlDbType.NChar, SqlDbType.NText, + SqlDbType.NVarChar, SqlDbType.Real, SqlDbType.SmallDateTime, SqlDbType.SmallInt, SqlDbType.SmallMoney, + SqlDbType.Structured, SqlDbType.Text, SqlDbType.Time, SqlDbType.Timestamp, SqlDbType.TinyInt, + SqlDbType.UniqueIdentifier, SqlDbType.VarBinary, SqlDbType.VarChar, SqlDbType.Variant, SqlDbType.Xml }) + { + testReader.ProviderType = dbtype; + + try + { + DataTable schemaTable = testReader.GetSchemaTable(); + + Assert.Fail(); + } + catch (ArgumentException) + { + } + } + } + + // UDT schema and type name set + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.UdtSchema = "Schema"; + testReader.UdtType = "Type"; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = null; + + foreach (SqlDbType dbtype in new List { SqlDbType.BigInt, SqlDbType.Binary, SqlDbType.Bit, SqlDbType.Char, SqlDbType.Date, + SqlDbType.DateTime, SqlDbType.DateTime2, SqlDbType.DateTimeOffset, SqlDbType.Decimal, SqlDbType.Float, + SqlDbType.Image, SqlDbType.Int, SqlDbType.Money, SqlDbType.NChar, SqlDbType.NText, + SqlDbType.NVarChar, SqlDbType.Real, SqlDbType.SmallDateTime, SqlDbType.SmallInt, SqlDbType.SmallMoney, + SqlDbType.Structured, SqlDbType.Text, SqlDbType.Time, SqlDbType.Timestamp, SqlDbType.TinyInt, + SqlDbType.UniqueIdentifier, SqlDbType.VarBinary, SqlDbType.VarChar, SqlDbType.Variant, SqlDbType.Xml }) + { + testReader.ProviderType = dbtype; + + try + { + DataTable schemaTable = testReader.GetSchemaTable(); + + Assert.Fail(); + } + catch (ArgumentException) + { + } + } + } + + // XML type name set + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = null; + testReader.XmlSchemaCollectionName = "Name"; + + foreach (SqlDbType dbtype in new List { SqlDbType.BigInt, SqlDbType.Binary, SqlDbType.Bit, SqlDbType.Char, SqlDbType.Date, + SqlDbType.DateTime, SqlDbType.DateTime2, SqlDbType.DateTimeOffset, SqlDbType.Decimal, SqlDbType.Float, + SqlDbType.Image, SqlDbType.Int, SqlDbType.Money, SqlDbType.NChar, SqlDbType.NText, + SqlDbType.NVarChar, SqlDbType.Real, SqlDbType.SmallDateTime, SqlDbType.SmallInt, SqlDbType.SmallMoney, + SqlDbType.Structured, SqlDbType.Text, SqlDbType.Time, SqlDbType.Timestamp, SqlDbType.TinyInt, + SqlDbType.UniqueIdentifier, SqlDbType.VarBinary, SqlDbType.VarChar, SqlDbType.Variant, SqlDbType.Udt }) + { + testReader.ProviderType = dbtype; + + try + { + DataTable schemaTable = testReader.GetSchemaTable(); + + Assert.Fail(); + } + catch (ArgumentException) + { + } + } + } + + // XML owning schema and type name set + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = null; + testReader.XmlSchemaCollectionOwningSchema = "Schema"; + testReader.XmlSchemaCollectionName = "Name"; + + foreach (SqlDbType dbtype in new List { SqlDbType.BigInt, SqlDbType.Binary, SqlDbType.Bit, SqlDbType.Char, SqlDbType.Date, + SqlDbType.DateTime, SqlDbType.DateTime2, SqlDbType.DateTimeOffset, SqlDbType.Decimal, SqlDbType.Float, + SqlDbType.Image, SqlDbType.Int, SqlDbType.Money, SqlDbType.NChar, SqlDbType.NText, + SqlDbType.NVarChar, SqlDbType.Real, SqlDbType.SmallDateTime, SqlDbType.SmallInt, SqlDbType.SmallMoney, + SqlDbType.Structured, SqlDbType.Text, SqlDbType.Time, SqlDbType.Timestamp, SqlDbType.TinyInt, + SqlDbType.UniqueIdentifier, SqlDbType.VarBinary, SqlDbType.VarChar, SqlDbType.Variant, SqlDbType.Udt }) + { + testReader.ProviderType = dbtype; + + try + { + DataTable schemaTable = testReader.GetSchemaTable(); + + Assert.Fail(); + } + catch (ArgumentException) + { + } + } + } + + // XML database, owning schema and type name set + using (BulkDataReaderSchemaTest testReader = new BulkDataReaderSchemaTest()) + { + testReader.AllowDBNull = false; + testReader.ColumnName = "Name"; + testReader.ColumnSize = null; + testReader.IsKey = false; + testReader.IsUnique = false; + testReader.NumericPrecision = null; + testReader.NumericScale = null; + testReader.UdtSchema = null; + testReader.UdtType = null; + testReader.XmlSchemaCollectionDatabase = "Database"; + testReader.XmlSchemaCollectionOwningSchema = "Schema"; + testReader.XmlSchemaCollectionName = "Name"; + + foreach (SqlDbType dbtype in new List { SqlDbType.BigInt, SqlDbType.Binary, SqlDbType.Bit, SqlDbType.Char, SqlDbType.Date, + SqlDbType.DateTime, SqlDbType.DateTime2, SqlDbType.DateTimeOffset, SqlDbType.Decimal, SqlDbType.Float, + SqlDbType.Image, SqlDbType.Int, SqlDbType.Money, SqlDbType.NChar, SqlDbType.NText, + SqlDbType.NVarChar, SqlDbType.Real, SqlDbType.SmallDateTime, SqlDbType.SmallInt, SqlDbType.SmallMoney, + SqlDbType.Structured, SqlDbType.Text, SqlDbType.Time, SqlDbType.Timestamp, SqlDbType.TinyInt, + SqlDbType.UniqueIdentifier, SqlDbType.VarBinary, SqlDbType.VarChar, SqlDbType.Variant, SqlDbType.Udt }) + { + testReader.ProviderType = dbtype; + + try + { + DataTable schemaTable = testReader.GetSchemaTable(); + + Assert.Fail(); + } + catch (ArgumentException) + { + } + } + } + } + + #endregion; + + #region Rowset tests + + /// + /// Test that is functioning correctly. + /// + /// + [Test] + public void CloseTest() + { + BulkDataReaderSubclass testReader = new BulkDataReaderSubclass(); + + testReader.Close(); + + Assert.IsTrue(testReader.IsClosed); + } + + /// + /// Test that is functioning correctly. + /// + /// + /// Because nested row sets are not supported, this should always return 0; + /// + /// + [Test] + public void DepthTest() + { + using (BulkDataReaderSubclass testReader = new BulkDataReaderSubclass()) + { + Assert.IsTrue(testReader.Read()); + + Assert.AreEqual(testReader.Depth, 0); + } + } + + /// + /// Test that is functioning correctly. + /// + /// + /// Because nested row sets are not supported, this should always return null; + /// + /// + [Test] + public void GetDataTest() + { + using (BulkDataReaderSubclass testReader = new BulkDataReaderSubclass()) + { + Assert.IsTrue(testReader.Read()); + + Assert.IsTrue(testReader.FieldCount > 0); + + Assert.IsNull(testReader.GetData(0)); + } + } + + /// + /// Test and related functions. + /// + /// + /// Uses to test legal schema combinations. + /// + [Test] + public void GetValueTest() + { + using (BulkDataReaderSubclass testReader = new BulkDataReaderSubclass()) + { + Assert.IsTrue(testReader.Read()); + + // this[int] + for (int column = 0; column < BulkDataReaderSubclass.ExpectedResultSet.Count; column++) + { + Assert.AreEqual(testReader[column], BulkDataReaderSubclass.ExpectedResultSet[column]); + } + + // this[string] + for (int column = 0; column < BulkDataReaderSubclass.ExpectedResultSet.Count; column++) + { + Assert.AreEqual(testReader[testReader.GetName(column)], BulkDataReaderSubclass.ExpectedResultSet[column]); + + Assert.AreEqual(testReader[testReader.GetName(column).ToUpperInvariant()], BulkDataReaderSubclass.ExpectedResultSet[column]); + } + + // GetValues + { + object[] values = new object[BulkDataReaderSubclass.ExpectedResultSet.Count]; + object[] expectedValues = new object[BulkDataReaderSubclass.ExpectedResultSet.Count]; + + Assert.AreEqual(testReader.GetValues(values), values.Length); + + BulkDataReaderSubclass.ExpectedResultSet.CopyTo(expectedValues, 0); + + Assert.IsTrue(BulkDataReaderTest.ArraysMatch(values, expectedValues)); + } + + // Typed getters + { + int currentColumn = 0; + + Assert.AreEqual(testReader.GetInt64(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + { + byte[] expectedResult = (byte[])BulkDataReaderSubclass.ExpectedResultSet[currentColumn]; + int expectedLength = expectedResult.Length; + byte[] buffer = new byte[expectedLength]; + + Assert.AreEqual(testReader.GetBytes(currentColumn, 0, buffer, 0, expectedLength), expectedLength); + + Assert.IsTrue(BulkDataReaderTest.ArraysMatch(buffer, expectedResult)); + } + currentColumn++; + + Assert.AreEqual(testReader.GetBoolean(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.IsDBNull(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn] == null); + currentColumn++; + + Assert.AreEqual(testReader.GetChar(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetChar(currentColumn), ((char[])BulkDataReaderSubclass.ExpectedResultSet[currentColumn])[0]); + currentColumn++; + + Assert.AreEqual(testReader.GetChar(currentColumn), ((string)BulkDataReaderSubclass.ExpectedResultSet[currentColumn])[0]); + currentColumn++; + + Assert.AreEqual(testReader.GetString(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + + { + char[] expectedResult = ((string)BulkDataReaderSubclass.ExpectedResultSet[currentColumn]).ToCharArray(); + int expectedLength = expectedResult.Length; + char[] buffer = new char[expectedLength]; + + Assert.AreEqual(testReader.GetChars(currentColumn, 0, buffer, 0, expectedLength), expectedLength); + + Assert.IsTrue(BulkDataReaderTest.ArraysMatch(buffer, expectedResult)); + } + + currentColumn++; + + Assert.AreEqual(testReader.GetDateTime(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetDateTime(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetDateTime(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetDateTime(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetDateTimeOffset(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetDateTimeOffset(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetDecimal(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetDouble(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + { + byte[] expectedResult = (byte[])BulkDataReaderSubclass.ExpectedResultSet[currentColumn]; + int expectedLength = expectedResult.Length; + byte[] buffer = new byte[expectedLength]; + + Assert.AreEqual(testReader.GetBytes(currentColumn, 0, buffer, 0, expectedLength), expectedLength); + + Assert.IsTrue(BulkDataReaderTest.ArraysMatch(buffer, expectedResult)); + } + currentColumn++; + + Assert.AreEqual(testReader.GetInt32(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetDecimal(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetString(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetString(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetString(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetString(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetFloat(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetDateTime(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetInt16(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetDecimal(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetString(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetTimeSpan(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetTimeSpan(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetByte(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetValue(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetGuid(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + { + byte[] expectedResult = (byte[])BulkDataReaderSubclass.ExpectedResultSet[currentColumn]; + int expectedLength = expectedResult.Length; + byte[] buffer = new byte[expectedLength]; + + Assert.AreEqual(testReader.GetBytes(currentColumn, 0, buffer, 0, expectedLength), expectedLength); + + Assert.IsTrue(BulkDataReaderTest.ArraysMatch(buffer, expectedResult)); + } + currentColumn++; + + { + byte[] expectedResult = (byte[])BulkDataReaderSubclass.ExpectedResultSet[currentColumn]; + int expectedLength = expectedResult.Length; + byte[] buffer = new byte[expectedLength]; + + Assert.AreEqual(testReader.GetBytes(currentColumn, 0, buffer, 0, expectedLength), expectedLength); + + Assert.IsTrue(BulkDataReaderTest.ArraysMatch(buffer, expectedResult)); + } + currentColumn++; + + Assert.AreEqual(testReader.GetString(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetString(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetValue(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetString(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetString(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetString(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetString(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + + Assert.AreEqual(testReader.GetString(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); + currentColumn++; + } + } + } + + /// + /// Test throws a when + /// the index is too small. + /// + /// + /// Uses to test the method. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void GetValueIndexTooSmallTest() + { + using (BulkDataReaderSubclass testReader = new BulkDataReaderSubclass()) + { + Assert.IsTrue(testReader.Read()); + + object result = testReader.GetValue(-1); + } + } + + /// + /// Test throws a when + /// the index is too large. + /// + /// + /// Uses to test the method. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void GetValueIndexTooLargeTest() + { + using (BulkDataReaderSubclass testReader = new BulkDataReaderSubclass()) + { + Assert.IsTrue(testReader.Read()); + + object result = testReader.GetValue(testReader.FieldCount); + } + } + + /// + /// Test throws a when + /// the index is too small. + /// + /// + /// Uses to test the method. + /// + /// + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void GetDataIndexTooSmallTest() + { + using (BulkDataReaderSubclass testReader = new BulkDataReaderSubclass()) + { + Assert.IsTrue(testReader.Read()); + + object result = testReader.GetData(-1); + } + } + + /// + /// Test throws a when + /// the index is too large. + /// + /// + /// Uses to test the method. + /// + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void GetDataIndexTooLargeTest() + { + using (BulkDataReaderSubclass testReader = new BulkDataReaderSubclass()) + { + Assert.IsTrue(testReader.Read()); + + object result = testReader.GetData(testReader.FieldCount); + } + } + + /// + /// Test that functions correctly. + /// + /// + [Test] + public void IsDBNullTest() + { + using (BulkDataReaderSubclass testReader = new BulkDataReaderSubclass()) + { + for (int currentColumn = 0; currentColumn < testReader.FieldCount; currentColumn++) + { + Assert.AreEqual(testReader.IsDBNull(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn] == null); + } + } + } + + /// + /// Test that is functioning correctly. + /// + /// + /// Because this is a single row set, this should always return false; + /// + /// + [Test] + public void NextResultTest() + { + using (BulkDataReaderSubclass testReader = new BulkDataReaderSubclass()) + { + Assert.IsFalse(testReader.NextResult()); + } + } + + /// + /// Test that is functioning correctly. + /// + /// + /// Because this row set represents a data source, this should always return -1; + /// + /// + [Test] + public void RecordsAffectedTest() + { + using (BulkDataReaderSubclass testReader = new BulkDataReaderSubclass()) + { + Assert.IsTrue(testReader.Read()); + + Assert.AreEqual(testReader.RecordsAffected, -1); + } + } + + #endregion + + #region Test IDisposable + + /// + /// Test that the interface is functioning correctly. + /// + /// + /// + [Test] + public void IDisposableTest() + { + // Test the Dispose method + { + BulkDataReaderSubclass testReader = new BulkDataReaderSubclass(); + + testReader.Dispose(); + + Assert.IsTrue(testReader.IsClosed); + } + + // Test the finalizer method + { + BulkDataReaderSubclass testReader = new BulkDataReaderSubclass(); + + testReader = null; + + GC.Collect(); + + GC.WaitForPendingFinalizers(); + } + } + + #endregion + + #region Utility + + /// + /// Do the two arrays match exactly? + /// + /// + /// The type of the array elements. + /// + /// + /// The first array. + /// + /// + /// The second array. + /// + /// + /// True if the arrays have the same length and contents. + /// + private static bool ArraysMatch(ElementType[] left, + ElementType[] right) + { + if (left == null) + { + throw new ArgumentNullException("left"); + } + else if (right == null) + { + throw new ArgumentNullException("left"); + } + + bool result = true; + + if (left.Length != right.Length) + { + result = false; + } + else + { + for (int currentIndex = 0; currentIndex < left.Length; currentIndex++) + { + result &= object.Equals(left[currentIndex], right[currentIndex]); + } + } + + return result; + } + + #endregion + + #region Test stubs + + /// + /// A subclass of used for testing its utility functions. + /// + private class BulkDataReaderSubclass : BulkDataReader + { + + #region Constructors + + /// + /// Constructor. + /// + public BulkDataReaderSubclass() + { + } + + #endregion + + #region BulkDataReader + + /// + /// See . + /// + /// + /// Returns . + /// + protected override string SchemaName + { + get { return BulkDataReaderTest.testSchemaName; } + } + + /// + /// See . + /// + /// + /// Returns . + /// + protected override string TableName + { + get { return BulkDataReaderTest.testTableName; } + } + + /// + /// See + /// + /// + /// Creates a schema row for the various values. + /// + protected override void AddSchemaTableRows() + { + AddSchemaTableRow("BigInt", null, null, null, true, false, false, SqlDbType.BigInt, null, null, null, null, null); + AddSchemaTableRow("Binary_20", 20, null, null, false, true, false, SqlDbType.Binary, null, null, null, null, null); + AddSchemaTableRow("Bit", null, null, null, false, false, true, SqlDbType.Bit, null, null, null, null, null); + AddSchemaTableRow("Bit_null", null, null, null, false, false, true, SqlDbType.Bit, null, null, null, null, null); + AddSchemaTableRow("Char_Char", 1, null, null, false, false, false, SqlDbType.Char, null, null, null, null, null); + AddSchemaTableRow("Char_Char_Array", 1, null, null, false, false, false, SqlDbType.Char, null, null, null, null, null); + AddSchemaTableRow("Char_String", 1, null, null, false, false, false, SqlDbType.Char, null, null, null, null, null); + AddSchemaTableRow("Char_20_String", 20, null, null, false, false, false, SqlDbType.Char, null, null, null, null, null); + AddSchemaTableRow("Date", null, null, null, false, false, false, SqlDbType.Date, null, null, null, null, null); + AddSchemaTableRow("DateTime", null, null, null, false, false, false, SqlDbType.DateTime, null, null, null, null, null); + AddSchemaTableRow("DateTime2", null, null, null, false, false, false, SqlDbType.DateTime2, null, null, null, null, null); + AddSchemaTableRow("DateTime2_5", null, 5, null, false, false, false, SqlDbType.DateTime2, null, null, null, null, null); + AddSchemaTableRow("DateTimeOffset", null, null, null, false, false, false, SqlDbType.DateTimeOffset, null, null, null, null, null); + AddSchemaTableRow("DateTimeOffset_5", null, 5, null, false, false, false, SqlDbType.DateTimeOffset, null, null, null, null, null); + AddSchemaTableRow("Decimal_20_10", null, 20, 10, false, false, false, SqlDbType.Decimal, null, null, null, null, null); + AddSchemaTableRow("Float_50", null, 50, null, false, false, false, SqlDbType.Float, null, null, null, null, null); + AddSchemaTableRow("Image", null, null, null, false, false, false, SqlDbType.Image, null, null, null, null, null); + AddSchemaTableRow("Int", null, null, null, false, false, false, SqlDbType.Int, null, null, null, null, null); + AddSchemaTableRow("Money", null, null, null, false, false, false, SqlDbType.Money, null, null, null, null, null); + AddSchemaTableRow("NChar_20", 20, null, null, false, false, false, SqlDbType.NChar, null, null, null, null, null); + AddSchemaTableRow("NText", null, null, null, false, false, false, SqlDbType.NText, null, null, null, null, null); + AddSchemaTableRow("NVarChar_20", 20, null, null, false, false, false, SqlDbType.NVarChar, null, null, null, null, null); + AddSchemaTableRow("NVarChar_Max", null, null, null, false, false, false, SqlDbType.NVarChar, null, null, null, null, null); + AddSchemaTableRow("Real", null, null, null, false, false, false, SqlDbType.Real, null, null, null, null, null); + AddSchemaTableRow("SmallDateTime", null, null, null, false, false, false, SqlDbType.SmallDateTime, null, null, null, null, null); + AddSchemaTableRow("SmallInt", null, null, null, false, false, false, SqlDbType.SmallInt, null, null, null, null, null); + AddSchemaTableRow("SmallMoney", null, null, null, false, false, false, SqlDbType.SmallMoney, null, null, null, null, null); + AddSchemaTableRow("Text", null, null, null, false, false, false, SqlDbType.Text, null, null, null, null, null); + AddSchemaTableRow("Time", null, null, null, false, false, false, SqlDbType.Time, null, null, null, null, null); + AddSchemaTableRow("Time_5", null, 5, null, false, false, false, SqlDbType.Time, null, null, null, null, null); + AddSchemaTableRow("TinyInt", null, null, null, false, false, false, SqlDbType.TinyInt, null, null, null, null, null); + AddSchemaTableRow("Udt", null, null, null, false, false, false, SqlDbType.Udt, BulkDataReaderTest.testUdtSchemaName, BulkDataReaderTest.testUdtName, null, null, null); + AddSchemaTableRow("UniqueIdentifier", null, null, null, false, false, false, SqlDbType.UniqueIdentifier, null, null, null, null, null); + AddSchemaTableRow("VarBinary_20", 20, null, null, false, false, false, SqlDbType.VarBinary, null, null, null, null, null); + AddSchemaTableRow("VarBinary_Max", null, null, null, false, false, false, SqlDbType.VarBinary, null, null, null, null, null); + AddSchemaTableRow("VarChar_20", 20, null, null, false, false, false, SqlDbType.VarChar, null, null, null, null, null); + AddSchemaTableRow("VarChar_Max", null, null, null, false, false, false, SqlDbType.VarChar, null, null, null, null, null); + AddSchemaTableRow("Variant", null, null, null, false, false, false, SqlDbType.Variant, null, null, null, null, null); + AddSchemaTableRow("Xml_Database", null, null, null, false, false, false, SqlDbType.Xml, null, null, BulkDataReaderTest.testXmlSchemaCollectionDatabaseName, BulkDataReaderTest.testXMLSchemaCollectionSchemaName, BulkDataReaderTest.testXMLSchemaCollectionName); + AddSchemaTableRow("Xml_Database_XML", null, null, null, false, false, false, SqlDbType.Xml, null, null, BulkDataReaderTest.testXmlSchemaCollectionDatabaseName, BulkDataReaderTest.testXMLSchemaCollectionSchemaName, BulkDataReaderTest.testXMLSchemaCollectionName); + AddSchemaTableRow("Xml_Schema", null, null, null, false, false, false, SqlDbType.Xml, null, null, null, BulkDataReaderTest.testXMLSchemaCollectionSchemaName, BulkDataReaderTest.testXMLSchemaCollectionName); + AddSchemaTableRow("Xml_Xml", null, null, null, false, false, false, SqlDbType.Xml, null, null, null, null, BulkDataReaderTest.testXMLSchemaCollectionName); + AddSchemaTableRow("Xml", null, null, null, false, false, false, SqlDbType.Xml, null, null, null, null, null); + } + + /// + /// The result set returned by the . + /// + public static readonly ReadOnlyCollection ExpectedResultSet = new ReadOnlyCollection(new List + { + (long)10, + new byte[20], + true, + null, + 'c', + new char[] { 'c' }, + "c", + "char 20", + DateTime.UtcNow, + DateTime.UtcNow, + DateTime.UtcNow, + DateTime.UtcNow, + DateTimeOffset.UtcNow, + DateTimeOffset.UtcNow, + (decimal)10.5, + (double)10.5, + new byte[20], + (int)10, + (decimal)10.5, + "nchar 20", + "ntext", + "nvarchar 20", + "nvarchar max", + (float)10.5, + DateTime.UtcNow, + (short)10, + (decimal)10.5, + "text", + DateTime.UtcNow.TimeOfDay, + DateTime.UtcNow.TimeOfDay, + (byte)10, + new object(), + Guid.NewGuid(), + new byte[20], + new byte[20], + "varchar 20", + "varchar max", + (int)10, + @"", + @"", + @"", + @"", + @"" + }); + + /// + /// See + /// + /// + /// The zero-based column ordinal. + /// + /// + /// The value of the column in . + /// + /// + public override object GetValue(int i) + { + return BulkDataReaderSubclass.ExpectedResultSet[i]; + } + + /// + /// The number of rows read. + /// + private int readCount = 0; + + /// + /// See + /// + /// + /// True if there are more rows; otherwise, false. + /// + /// + public override bool Read() + { + return readCount++ < 1; + } + + #endregion + + } + + private class BulkDataReaderSchemaTest : BulkDataReader + { + + #region Properties + + /// + /// Is the column nullable (i.e. optional)? + /// + public bool AllowDBNull { get; set; } + + /// + /// The name of the column. + /// + public string ColumnName { get; set; } + + /// + /// The size of the column which may be null if not applicable. + /// + public int? ColumnSize { get; set; } + + /// + /// Is the column part of the primary key? + /// + public bool IsKey { get; set; } + + /// + /// Are the column values unique (i.e. never duplicated)? + /// + public bool IsUnique { get; set; } + + /// + /// The precision of the column which may be null if not applicable. + /// + public short? NumericPrecision { get; set; } + + /// + /// The scale of the column which may be null if not applicable. + /// + public short? NumericScale { get; set; } + + /// + /// The corresponding . + /// + public SqlDbType ProviderType { get; set; } + + /// + /// The schema name of the UDT. + /// + public string UdtSchema { get; set; } + + /// + /// The type name of the UDT. + /// + public string UdtType { get; set; } + + /// + /// For XML columns the schema collection's database name. Otherwise, null. + /// + public string XmlSchemaCollectionDatabase { get; set; } + + /// + /// For XML columns the schema collection's name. Otherwise, null. + /// + public string XmlSchemaCollectionName { get; set; } + + /// + /// For XML columns the schema collection's schema name. Otherwise, null. + /// + public string XmlSchemaCollectionOwningSchema { get; set; } + + #endregion + + #region Constructors + + /// + /// Constructor. + /// + public BulkDataReaderSchemaTest() + { + } + + #endregion + + #region BulkDataReader + + /// + /// See . + /// + /// + /// Returns . + /// + protected override string SchemaName + { + get { return BulkDataReaderTest.testSchemaName; } + } + + /// + /// See . + /// + /// + /// Returns . + /// + protected override string TableName + { + get { return BulkDataReaderTest.testTableName; } + } + + /// + /// See + /// + /// + /// Creates a schema row for the various values. + /// + protected override void AddSchemaTableRows() + { + AddSchemaTableRow(this.ColumnName, + this.ColumnSize, + this.NumericPrecision, + this.NumericScale, + this.IsUnique, + this.IsKey, + this.AllowDBNull, + this.ProviderType, + this.UdtSchema, + this.UdtType, + this.XmlSchemaCollectionDatabase, + this.XmlSchemaCollectionOwningSchema, + this.XmlSchemaCollectionName); + } + + /// + /// See + /// + /// + /// The test stub is only for testing schema functionality and behaves as if it has no rows. + /// + /// + /// The zero-based column ordinal. + /// + /// + /// Never returns. + /// + /// + public override object GetValue(int i) + { + throw new InvalidOperationException("No data."); + } + + + /// + /// See + /// + /// + /// False. + /// + /// + public override bool Read() + { + return false; + } + + #endregion + + } + + #endregion + } +} diff --git a/src/Umbraco.Tests/Persistence/NPocoExtensionsTest.cs b/src/Umbraco.Tests/Persistence/NPocoExtensionsTest.cs index b38fa96d0c..5ef18efea0 100644 --- a/src/Umbraco.Tests/Persistence/NPocoExtensionsTest.cs +++ b/src/Umbraco.Tests/Persistence/NPocoExtensionsTest.cs @@ -1,9 +1,13 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Persistence @@ -15,7 +19,7 @@ namespace Umbraco.Tests.Persistence public class NPocoExtensionsTest : TestWithDatabaseBase { [Test] - public void Can_Bulk_Insert() + public void Can_Bulk_Insert_One_By_One() { // Arrange var db = DatabaseContext.Database; @@ -24,25 +28,178 @@ namespace Umbraco.Tests.Persistence for (var i = 0; i < 1000; i++) { servers.Add(new ServerRegistrationDto - { - ServerAddress = "address" + i, - ServerIdentity = "computer" + i, - DateRegistered = DateTime.Now, - IsActive = true, - DateAccessed = DateTime.Now - }); + { + ServerAddress = "address" + i, + ServerIdentity = "computer" + i, + DateRegistered = DateTime.Now, + IsActive = true, + DateAccessed = DateTime.Now + }); } // Act using (ProfilingLogger.TraceDuration("starting insert", "finished insert")) { - db.BulkInsertRecordsWithTransaction(SqlSyntax, servers); + using (var tr = db.GetTransaction()) + { + db.BulkInsertRecords(servers, false); + tr.Complete(); + } } // Assert Assert.That(db.ExecuteScalar("SELECT COUNT(*) FROM umbracoServer"), Is.EqualTo(1000)); } + [Test] + public void Can_Bulk_Insert_One_By_One_Transaction_Rollback() + { + // Arrange + var db = DatabaseContext.Database; + + var servers = new List(); + for (var i = 0; i < 1000; i++) + { + servers.Add(new ServerRegistrationDto + { + ServerAddress = "address" + i, + ServerIdentity = "computer" + i, + DateRegistered = DateTime.Now, + IsActive = true, + DateAccessed = DateTime.Now + }); + } + + // Act + using (ProfilingLogger.TraceDuration("starting insert", "finished insert")) + { + using (var tr = db.GetTransaction()) + { + db.BulkInsertRecords(servers, false); + //don't call complete here - the trans will be rolled back + } + } + + // Assert + Assert.That(db.ExecuteScalar("SELECT COUNT(*) FROM umbracoServer"), Is.EqualTo(0)); + } + + + [NUnit.Framework.Ignore("Ignored because you need to configure your own SQL Server to test thsi with")] + [Test] + public void Can_Bulk_Insert_Native_Sql_Server_Bulk_Inserts() + { + // create the db + // prob not what we want, this is not a real database, but hey, the test is ignored anyways + // we'll fix this when we have proper testing infrastructure + var dbSqlServer = TestObjects.GetUmbracoSqlServerDatabase(new DebugDiagnosticsLogger()); + + //drop the table + dbSqlServer.Execute("DROP TABLE [umbracoServer]"); + + //re-create it + dbSqlServer.Execute(@"CREATE TABLE [umbracoServer]( + [id] [int] IDENTITY(1,1) NOT NULL, + [address] [nvarchar](500) NOT NULL, + [computerName] [nvarchar](255) NOT NULL, + [registeredDate] [datetime] NOT NULL CONSTRAINT [DF_umbracoServer_registeredDate] DEFAULT (getdate()), + [lastNotifiedDate] [datetime] NOT NULL, + [isActive] [bit] NOT NULL, + [isMaster] [bit] NOT NULL, + CONSTRAINT [PK_umbracoServer] PRIMARY KEY CLUSTERED +( + [id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] +)"); + var data = new List(); + for (var i = 0; i < 1000; i++) + { + data.Add(new ServerRegistrationDto + { + ServerAddress = "address" + i, + ServerIdentity = "computer" + i, + DateRegistered = DateTime.Now, + IsActive = true, + DateAccessed = DateTime.Now + }); + } + + using (var tr = dbSqlServer.GetTransaction()) + { + dbSqlServer.BulkInsertRecords(data); + tr.Complete(); + } + + // Assert + Assert.That(dbSqlServer.ExecuteScalar("SELECT COUNT(*) FROM umbracoServer"), Is.EqualTo(1000)); + } + + [Test] + public void Can_Bulk_Insert_Native_Sql_Bulk_Inserts() + { + // Arrange + var db = DatabaseContext.Database; + + var servers = new List(); + for (var i = 0; i < 1000; i++) + { + servers.Add(new ServerRegistrationDto + { + ServerAddress = "address" + i, + ServerIdentity = "computer" + i, + DateRegistered = DateTime.Now, + IsActive = true, + DateAccessed = DateTime.Now + }); + } + + // Act + using (ProfilingLogger.TraceDuration("starting insert", "finished insert")) + { + using (var tr = db.GetTransaction()) + { + db.BulkInsertRecords(servers); + tr.Complete(); + } + } + + // Assert + Assert.That(db.ExecuteScalar("SELECT COUNT(*) FROM umbracoServer"), Is.EqualTo(1000)); + } + + [Test] + public void Can_Bulk_Insert_Native_Sql_Bulk_Inserts_Transaction_Rollback() + { + // Arrange + var db = DatabaseContext.Database; + + var servers = new List(); + for (var i = 0; i < 1000; i++) + { + servers.Add(new ServerRegistrationDto + { + ServerAddress = "address" + i, + ServerIdentity = "computer" + i, + DateRegistered = DateTime.Now, + IsActive = true, + DateAccessed = DateTime.Now + }); + } + + // Act + using (ProfilingLogger.TraceDuration("starting insert", "finished insert")) + { + using (var tr = db.GetTransaction()) + { + db.BulkInsertRecords(servers); + //don't call complete here - the trans will be rolled back + } + } + + // Assert + Assert.That(db.ExecuteScalar("SELECT COUNT(*) FROM umbracoServer"), Is.EqualTo(0)); + } + [Test] public void Generate_Bulk_Import_Sql() { @@ -53,23 +210,22 @@ namespace Umbraco.Tests.Persistence for (var i = 0; i < 2; i++) { servers.Add(new ServerRegistrationDto - { - ServerAddress = "address" + i, - ServerIdentity = "computer" + i, - DateRegistered = DateTime.Now, - IsActive = true, - DateAccessed = DateTime.Now - }); + { + ServerAddress = "address" + i, + ServerIdentity = "computer" + i, + DateRegistered = DateTime.Now, + IsActive = true, + DateAccessed = DateTime.Now + }); } db.OpenSharedConnection(); // Act - string[] sql; - db.GenerateBulkInsertCommand(servers.ToArray(), db.Connection, out sql); + var commands = db.GenerateBulkInsertCommands(servers.ToArray()); db.CloseSharedConnection(); // Assert - Assert.That(sql[0], + Assert.That(commands[0].CommandText, Is.EqualTo("INSERT INTO [umbracoServer] ([umbracoServer].[address], [umbracoServer].[computerName], [umbracoServer].[registeredDate], [umbracoServer].[lastNotifiedDate], [umbracoServer].[isActive], [umbracoServer].[isMaster]) VALUES (@0,@1,@2,@3,@4,@5), (@6,@7,@8,@9,@10,@11)")); } @@ -96,13 +252,12 @@ namespace Umbraco.Tests.Persistence db.OpenSharedConnection(); // Act - string[] sql; - db.GenerateBulkInsertCommand(servers.ToArray(), db.Connection, out sql); + var commands = db.GenerateBulkInsertCommands(servers.ToArray()); db.CloseSharedConnection(); // Assert - Assert.That(sql.Length, Is.EqualTo(5)); - foreach (var s in sql) + Assert.That(commands.Length, Is.EqualTo(5)); + foreach (var s in commands.Select(x => x.CommandText)) { Assert.LessOrEqual(Regex.Matches(s, "@\\d+").Count, 2000); } diff --git a/src/Umbraco.Tests/Persistence/PetaPocoCachesTest.cs b/src/Umbraco.Tests/Persistence/PetaPocoCachesTest.cs index 965341d7ea..bbec78805c 100644 --- a/src/Umbraco.Tests/Persistence/PetaPocoCachesTest.cs +++ b/src/Umbraco.Tests/Persistence/PetaPocoCachesTest.cs @@ -1,7 +1,11 @@ -using System; +using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using System.Threading; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Tests.Services; using Umbraco.Tests.TestHelpers; @@ -15,6 +19,8 @@ namespace Umbraco.Tests.Persistence [TestFixture, Ignore] public class PetaPocoCachesTest : BaseServiceTest { + +#if DEBUG /// /// This tests the peta poco caches /// @@ -189,5 +195,6 @@ namespace Umbraco.Tests.Persistence contentService.MoveToRecycleBin(content1); } +#endif } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index cc30799907..94461196cb 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -521,7 +521,7 @@ namespace Umbraco.Tests.Persistence.Repositories { var repository = CreateRepository(unitOfWork); var contentType = repository.Get(NodeDto.NodeIdSeed + 1); - var child1 = MockedContentTypes.CreateSimpleContentType("aabc", "aabc", contentType, randomizeAliases: true); + var child1 = MockedContentTypes.CreateSimpleContentType("abc", "abc", contentType, randomizeAliases: true); repository.AddOrUpdate(child1); var child3 = MockedContentTypes.CreateSimpleContentType("zyx", "zyx", contentType, randomizeAliases: true); repository.AddOrUpdate(child3); @@ -535,7 +535,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Assert Assert.That(contentTypes.Count(), Is.EqualTo(3)); Assert.AreEqual("a123", contentTypes.ElementAt(0).Name); - Assert.AreEqual("aabc", contentTypes.ElementAt(1).Name); + Assert.AreEqual("abc", contentTypes.ElementAt(1).Name); Assert.AreEqual("zyx", contentTypes.ElementAt(2).Name); } diff --git a/src/Umbraco.Tests/Services/PerformanceTests.cs b/src/Umbraco.Tests/Services/PerformanceTests.cs index a561be8d6f..92c8a625a7 100644 --- a/src/Umbraco.Tests/Services/PerformanceTests.cs +++ b/src/Umbraco.Tests/Services/PerformanceTests.cs @@ -200,7 +200,7 @@ namespace Umbraco.Tests.Services //now we insert each record for the ones we've deleted like we do in the content service. var xmlItems = nodes.Select(node => new ContentXmlDto { NodeId = node.NodeId, Xml = UpdatedXmlStructure }).ToList(); - DatabaseContext.Database.BulkInsertRecordsWithTransaction(SqlSyntax, xmlItems); + DatabaseContext.Database.BulkInsertRecordsWithTransaction(xmlItems); } } @@ -221,7 +221,7 @@ namespace Umbraco.Tests.Services INNER JOIN cmsContent ON cmsContentXml.nodeId = cmsContent.nodeId)"); - DatabaseContext.Database.BulkInsertRecords(SqlSyntax, xmlItems); + DatabaseContext.Database.BulkInsertRecords(xmlItems); tr.Complete(); } @@ -289,7 +289,7 @@ namespace Umbraco.Tests.Services Path = "" }); } - DatabaseContext.Database.BulkInsertRecordsWithTransaction(SqlSyntax, nodes); + DatabaseContext.Database.BulkInsertRecordsWithTransaction(nodes); //re-get the nodes with ids var sql = DatabaseContext.Database.Sql(); @@ -299,11 +299,11 @@ namespace Umbraco.Tests.Services //create the cmsContent data, each with a new content type id (so we can query on it later if needed) var contentTypeId = 0; var cmsContentItems = nodes.Select(node => new ContentDto { NodeId = node.NodeId, ContentTypeId = contentTypeId++ }).ToList(); - DatabaseContext.Database.BulkInsertRecordsWithTransaction(SqlSyntax, cmsContentItems); + DatabaseContext.Database.BulkInsertRecordsWithTransaction(cmsContentItems); //create the xml data var xmlItems = nodes.Select(node => new ContentXmlDto { NodeId = node.NodeId, Xml = TestXmlStructure }).ToList(); - DatabaseContext.Database.BulkInsertRecordsWithTransaction(SqlSyntax, xmlItems); + DatabaseContext.Database.BulkInsertRecordsWithTransaction(xmlItems); return nodes; } diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index 4e136a5c22..6d4b4188ff 100644 --- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs @@ -205,16 +205,16 @@ namespace Umbraco.Tests.TestHelpers public static void DeleteDirectory(string path) { - if (Directory.Exists(path) == false) return; - Try(() => { + if (Directory.Exists(path) == false) return; foreach (var file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)) File.Delete(file); }); Try(() => { + if (Directory.Exists(path) == false) return; Directory.Delete(path, true); }); } diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 9dcc6d4526..42a5132564 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -236,6 +236,7 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less index 52c4052975..e0527aefcb 100644 --- a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less +++ b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less @@ -21,7 +21,6 @@ body { font-size: 14px; line-height: 20px; color: #343434; - background-color: #F5F5F5; -webkit-transition: all 0.2s ease-in-out; -moz-transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out; diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 00cdd4f9b1..85ab4e6e56 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -157,7 +157,7 @@ h5.-black { } .controls-row { - padding-top: 5px; + padding-bottom: 5px; margin-left: 240px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 16d48807eb..573f218fb1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -232,11 +232,11 @@ ul.color-picker li a { max-height: none !important; } -.umb-sortable-thumbnails .icon-holder { +.umb-sortable-thumbnails .umb-icon-holder { text-align: center; } -.umb-sortable-thumbnails .icon-holder .icon{ +.umb-sortable-thumbnails .umb-icon-holder .icon{ font-size: 40px; line-height: 50px; color: @gray; diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js index e29df0b9ae..e5fba69a88 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js @@ -34,6 +34,9 @@ file: file }).progress(function (evt) { + // set view state to uploading + vm.state = 'uploading'; + // calculate progress in percentage var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10); diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html index 07fb21d00b..499e844588 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html @@ -37,10 +37,8 @@ - or click here to choose files -
- {{vm.zipFile.serverErrorMessage}} -
+ @@ -53,6 +51,41 @@ +
+ + + + ← Upload another package + + + +
+ +
+
+
+ +
+
+ +

Uploading package

+ + + + +
+ {{ vm.zipFile.serverErrorMessage }} +
+ +
+
+
+ +
+ +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html index 68d2fc6f4d..f3aab992dd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html @@ -11,7 +11,7 @@ - + {{image.name}} diff --git a/src/Umbraco.Web.UI/config/Dashboard.config b/src/Umbraco.Web.UI/config/Dashboard.config index 53227106f0..3e7e3c86cf 100644 --- a/src/Umbraco.Web.UI/config/Dashboard.config +++ b/src/Umbraco.Web.UI/config/Dashboard.config @@ -104,16 +104,6 @@ -
- - developer - - - - /App_Plugins/ModelsBuilder/modelsbuilder.htm - - -
content @@ -124,6 +114,16 @@
+
+ + developer + + + + /App_Plugins/ModelsBuilder/modelsbuilder.htm + + +
content diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 0df6e5508a..104c81be7e 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -304,8 +304,16 @@ Vælg editor - Rediger de forskellige sprogversioner for ordbogselementet '%0%' herunder. Du tilføjer flere sprog under 'sprog' i menuen til venstre + Du tilføjer flere sprog under 'sprog' i menuen til venstre + ]]> Kulturnavn + Rediger navnet på ordbogselementet. + + + Indtast dit brugernavn diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 92ff74fdc5..5f0180259a 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -144,7 +144,7 @@ Role Member Type No date chosen - Page Title + Link title Properties This document is published but is not visible because the parent '%0%' is unpublished This document is published but is not in the cache @@ -328,6 +328,12 @@ Edit the different language versions for the dictionary item '%0%' below
You can add additional languages under the 'languages' in the menu on the left ]]> Culture Name + Edit the key of the dictionary item. + + + Enter your username diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 609177d893..da88e84e10 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -145,7 +145,7 @@ Role Member Type No date chosen - Page Title + Link title Properties This document is published but is not visible because the parent '%0%' is unpublished This document is published but is not in the cache @@ -329,6 +329,12 @@ Edit the different language versions for the dictionary item '%0%' below
You can add additional languages under the 'languages' in the menu on the left ]]> Culture Name + Edit the key of the dictionary item. + + + Enter your username diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml index 8b8bdbf355..b99b5a867e 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml @@ -26,7 +26,8 @@ Publier Dépublier Rafraîchir - Republier le site entier + Republier le site tout entier + Récupérer Permissions Version antérieure Envoyer pour publication @@ -39,13 +40,13 @@ Permission refusée. - Ajouter nouveau domaine + Ajouter un nouveau domaine Supprimer Noeud invalide. Domaine invalide. Domaine déjà assigné. Domaine - Langage + Langue Nouveau domaine '%0%' créé Domaine '%0%' supprimé Domaine '%0%' déjà assigné @@ -53,24 +54,25 @@ "https://www.example.com/".

Les domaines contenant un chemin d'un niveau sont autorisés, ex : "example.com/en". Pour autant, cela devrait être évité. Utilisez plutôt la gestion des noms d'hôte.]]> Domaine '%0%' mis à jour - Editer les domaines + Editer les domaines actuels Hériter Culture - ou hériter la culture des noeuds parent. S'appliquera aussi'
- au noeud courant, à moins qu'un domaine soit appliqué aussi.]]>
+ ou hériter de la culture des noeuds parents. S'appliquera aussi
+ au noeud courant, à moins qu'un domaine ci-dessous soit aussi d'application.]]>
Domaines - Voir pour + Aperçu pour + Vider la sélection Choisir Choisir le répertoire courant Faire autre chose Gras Annuler l'indentation de paragraphe - Insérer champ de formulaire - Insérer une entête graphique + Insérer un champ de formulaire + Insérer un entête graphique Editer le HTML Indenter le paragraphe Italique @@ -78,42 +80,47 @@ Justifier à gauche Justifier à droite Insérer un lien - Insérer une ancre + Insérer un lien local (ancre) Liste à puces Liste numérique Insérer une macro Insérer une image + Retourner à la liste Editer les relations Sauver Sauver et publier Sauver et envoyer pour approbation + Sauver la mise en page de la liste Prévisualiser La prévisualisation est désactivée car aucun modèle n'a été assigné. Choisir un style Afficher les styles Insérer un tableau + Générer les modèles + Sauver et générer les modèles - Pour changer le type de document du contenu séléctionné, choisissez un type valide pour cet emplacement, qui soit conforme à la structure des types de documents. - Puis modifiez si nécessaire le mappage des propriétés du type actuel vers le nouveau, et cliquez sur Sauver + Pour changer le type de document du contenu séléctionné, faites d'abord un choix dans la liste des types valides à cet endroit. + Puis modifiez si nécessaire la correspondance des propriétés du type actuel vers le nouveau, et cliquez sur Sauver. Le contenu a été republié. Propriété actuelle Type actuel - Le type de document ne peut être changé, il n'y a pas d'alternatives valides pour cet emplacement. + Le type de document ne peut être changé car il n'y a pas d'alternative valide à cet endroit. Une alternative sera valide si elle est autorisée sous le parent du contenu sélectionné et si tous les éléments de contenu enfants existants peuvent être créés avec celle-ci. Type de document modifié - Mapper les propriétés - Mapper à la propriété + Faire correspondre les propriétés + Faire correspondre à la propriété Nouveau modèle Nouveau type aucun Contenu Choisir le nouveau Type de Document - Le type de document du contenu séléctionné a a bien été changé en [new type] et les propriétés suivantes mappées : + Le type de document du contenu séléctionné a bien été changé en [new type] et les correspondances de propriétés suivantes effectuées : en - Impossible de terminer le mappage des propriétés car une ou plus des propriétés ont plus de un mappage défini. - Seuls les types de documents valides pour cet emplacement sont affichés. + Impossible de terminer la correspondance des propriétés car une ou plusieurs propriétés ont plus d'une correspondance définie. + Seuls les types de documents valides à cet endroit sont affichés. + A été publié A propos de cette page Alias (comment décririez-vous l'image oralement) @@ -127,9 +134,10 @@ Type de Document Edition Expire le - Cet élément a été changé après la publication + Cet élément a été modifié après la publication Cet élément n'est pas publié Dernière publication + Il n'y a aucun élément à afficher Il n'y a aucun élément à afficher dans cette liste. Type de Média Lien vers des média(s) @@ -137,99 +145,116 @@ Rôle Type de membre Aucune date choisie - Titre de page + Titre de la page Propriétés - Ce document est publié mais n'est pas visible car son parent '%0%' est dépublié + Ce document est publié mais n'est pas visible car son parent '%0%' n'est pas publié Oups : ce document est publié mais n'est pas présent dans le cache (erreur interne Umbraco) + Oups: impossible d'obtenir cet url (erreur interne - voir fichier log) + Oups: ce document est publié mais son url entrerait en collision avec le contenu %0% Publier Statut de publication Publié le Dépublié le Supprimer la date Ordre de tri mis à jour - Pour trier les noeuds, faites les glisser à l'aide de la souris ou cliquez sur les entêtes de colonne. Vous pouvez séléctionner plusieurs noeuds en gardant la touche "shift" ou "ctrl" enfoncée pendant votre séléction. + Pour trier les noeuds, faites-les simplement glisser à l'aide de la souris ou cliquez sur les entêtes de colonne. Vous pouvez séléctionner plusieurs noeuds en gardant la touche "shift" ou "ctrl" enfoncée pendant votre séléction. Statistiques Titre (optionnel) + Texte alternatif (optionnel) Type - Dépublié + Dépublier Dernière édition Date/heure à laquelle ce document a été édité - Supprimer fichier(s) + Supprimer le(s) fichier(s) Lien vers un document Membre du/des groupe(s) - Pas un membre du/des groupe(s) - Elements enfants + Pas membre du/des groupe(s) + Eléments enfants Cible - + Ceci se traduit par l'heure suivante sur le serveur : + Qu'est-ce que cela signifie?]]> + Cliquez pour télécharger Faites glisser vos fichier ici... + Lien vers le média + ou cliquez ici pour choisir un fichier + Les seuls types de fichiers autorisés sont + Impossible de télécharger ce fichier, il n'a pas un type de fichier autorisé. + La taille maximum de fichier est + + + Créer un nouveau membre + Tous les membres Où voulez-vous créer le nouveau %0% Créer un élément sous Choisissez un type et un titre - "Types de documents".]]> - "Types de médias".]]> + "Types de documents".]]> + "Types de médias".]]> + Type de document sans modèle + Nouveau répertoire + Nouveau type de données Parcourir votre site - Cacher - Si Umbraco ne s'ouvre pas, vous devriez peut-être autoriser l'ouverture des popups pour ce site. + Si Umbraco ne s'ouvre pas, peut-être devez-vous autoriser l'ouverture des popups pour ce site. s'est ouvert dans une nouvelle fenêtre Redémarrer Visiter Bienvenue - Stay - Discard changes - You have unsaved changes - Are you sure you want to navigate away from this page? - you have unsaved changes + Rester + Invalider les changements + Vous avez des changements en cours + Etes-vous certain(e) de vouloir quitter cette page? - vous avez des changements en cours - Done - - Deleted %0% item - Deleted %0% items - Deleted %0% out of %1% item - Deleted %0% out of %1% items - - Published %0% item - Published %0% items - Published %0% out of %1% item - Published %0% out of %1% items - - Unpublished %0% item - Unpublished %0% items - Unpublished %0% out of %1% item - Unpublished %0% out of %1% items - - Moved %0% item - Moved %0% items - Moved %0% out of %1% item - Moved %0% out of %1% items - - Copied %0% item - Copied %0% items - Copied %0% out of %1% item - Copied %0% out of %1% items - + Terminé + + %0% élément supprimé + %0% éléments supprimés + %0% élément sur %1% supprimé + %0% éléments sur %1% supprimés + + %0% élément publié + %0% éléments publiés + %0% élément sur %1% publié + %0% éléments sur %1% publiés + + %0% élément dépublié + %0% éléments dépubliés + %0% élément sur %1% dépublié + %0% éléments sur %1% dépubliés + + %0% élément déplacé + %0% éléments déplacés + %0% élément sur %1% déplacé + %0% éléments sur %1% déplacés + + %0% élément copié + %0% éléments copiés + %0% élément sur %1% copié + %0% éléments sur %1% copiés + - Name + Nom Gérer les noms d'hôtes Fermer cette fenêtre - Êtes-vous sûr de vouloir supprimer - Êtes-vous sûr de vouloir désactiver + Êtes-vous certain(e) de vouloir supprimer + Êtes-vous certain(e) de vouloir désactiver Cochez cette case pour confirmer la suppression de %0% élément(s) - Êtes-vous sûr ? - Êtes-vous sûr ? + Êtes-vous certain(e)? + Êtes-vous certain(e)? Couper Editer une entrée du Dictionnaire - Editer le langage - Insérer une ancre + Modifier la langue + Insérer un lien local (ancre) Insérer un caractère - Insérer une entête graphique + Insérer un entête graphique Insérer une image Insérer un lien Insérer une macro @@ -237,75 +262,124 @@ Dernière modification Lien Lien interne : - Si vous utilisez des ancres, insérer # avant le lien - Ouvrir dans une nouvelle fenêtre ? + Si vous utilisez des ancres, insérez # au début du lien + Ouvrir dans une nouvelle fenêtre? Paramètres de macro Cette macro ne contient aucune propriété éditable Coller Editer les permissions pour Les éléments dans la corbeille sont en cours de suppression. Ne fermez pas cette fenêtre avant que cette opération soit terminée. La corbeille est maintenant vide - Les éléments supprimés de la corbeille sont supprimés définitivement - regexlib.com a actuellement des problèmes sur lesquels nous n'avons aucun contrôle. Excusez-nous pour le désagrément.]]> - Rechercher une expression régulière Search for a regular expression to add validation to a form field. Example: 'email, 'zip-code' 'url' + Les éléments supprimés de la corbeille seront supprimés définitivement + regexlib.com rencontre actuellement des problèmes sur lesquels nous n'avons aucun contrôle. Nous sommes sincèrement désolés pour le désagrément.]]> + Rechercher une expression régulière à ajouter pour la validation d'un champ de formulaire. Exemple: 'email, 'zip-code', 'url' Supprimer la macro - Champ requis + Champ obligatoire Le site a été réindéxé - Le cache du site a été mis à jour. Tous les contenus publiés sont à jour. Et tous les contenus dépubliés sont rendus invisibles. - Le cache du site va être mis à jour. Tous les contenus publiés seront à jour. Et tous les contenus dépubliés seront rendus invisibles. + Le cache du site a été mis à jour. Tous les contenus publiés sont maintenant à jour. Et tous les contenus dépubliés sont restés invisibles. + Le cache du site va être mis à jour. Tous les contenus publiés seront mis à jour. Et tous les contenus dépubliés resteront invisibles. Nombre de colonnes Nombre de lignes - Définir un placeholder id en mettant un ID sur votre placeholder vous pouvez injecter du contenu à cet endroit depuis vos modèles enfants, - en faisant référence à cet id au sein d'un élément <asp:content />.]]> - Séléctionnez un placeholder id depuis la liste ci-dessous. Vous pouvez seulement - choisir un ID depuis le modèle parent.]]> - Cliquez pour voir l'image en taille maximale - Séléctionner un élément + Définir un placeholder ID. En mettant un ID sur votre placeholder, vous pouvez injecter du contenu à cet endroit depuis les modèles enfants, + en faisant référence à cet ID au sein d'un élément <asp:content />.]]> + Séléctionnez un placeholder id dans la liste ci-dessous. Vous pouvez seulement + choisir un ID se trouvant dans le parent du modèle actuel.]]> + Cliquez sur l'image pour la voir en taille réelle + Sélectionner un élément Voir l'élément de cache + Créer un répertoire... + + Lier à l'original + La communauté la plus amicale + + Lier à la page + + Ouvre le document lié dans une nouvelle fenêtre ou un nouvel onglet + Ouvre le document lié dans l'entièreté de la fenêtre + Ouvre le document lié dans le conteneur parent + + Lier à un media + + Sélectionner le media + Sélectionner l'icône + Sélectionner l'élément + Sélectionner le lien + Sélectionner la macro + Sélectionner le contenu + Sélectionner le membre + Sélectionner le groupe de membres + + Il n'y a pas de paramètres pour cette macro + + Fournisseurs externes d'identification + Détails de l'exception + Trace d'exécution + Exception interne + + Liez votre + Enlevez votre + + compte + + Sélectionner un éditeur %0%' ci-dessous
Vous pouvez ajouter d'autres langages depuis le menu ci-dessous "Langages". + Editez les différentes versions de langues pour l'élément de dictionaire '%0%' ci-dessous.
Vous pouvez ajouter d'autres langues depuis le menu ci-dessous "Langues". ]]>
Nom de Culture Votre nom d'utilisateur Votre mot de passe + Confirmation de votre mot de passe Nommer %0%... Entrez un nom... + Libellé... + Entrez une description... Rechercher... Filtrer... + Ajouter des tags (appuyer sur enter entre chaque tag)... + Entrez votre email + + Autoriser à la racine + Seuls les Types de Contenu qui ont ceci coché peuvent être créés au niveau racine des arborescences de contenu et de media Types de noeuds enfants autorisés + Composition de Type de Documents Créer - Supprimer onglet + Supprimer l'onglet Description Nouvel onglet Onglet Miniature Activer la vue liste + Configure l'élément de contenu de sorte à afficher une liste de ses enfants que l'on peut trier et filtrer, les enfants ne seront pas affichés dans l'arborescence + Liste courante + Le type de donnée de la liste courante + Créer une liste personnalisée + Supprimer la liste personnalisée - Ajouter une prévaleur - Type de données en base de donées + Ajouter une valeur de base + Type de donnée en base de donées GUID du Property Editor Property editor Boutons Activer les paramètres avancés pour Activer le menu contextuel Taille maximale par défaut des images insérées - CSS relatives + CSS associées Afficher le libellé Largeur et hauteur - Vos données ont été sauvegardées, mais avant de publier votre page il y a des erreurs que vous devez corriger : - Le Membership Provider n'autorise pas le changement des mots de passe (EnablePasswordRetrieval doit être définit à true) + Vos données ont été sauvegardées, mais avant de pouvoir publier votre page, il y a des erreurs que vous devez corriger : + Le Membership Provider n'autorise pas le changement des mots de passe (EnablePasswordRetrieval doit être défini à true) %0% existe déjà - Il y a des erreurs : - Il y a des erreurs : + Des erreurs sont survenues : + Des erreurs sont survenues : Le mot de passe doit contenir un minimum de %0% caractères et contenir au moins %1% caractère(s) non-alphanumerique %0% doit être un entier Le champ %0% dans l'onglet %1% est obligatoire @@ -314,23 +388,30 @@ %0% n'est pas correctement formaté + Le serveur a retourné une erreur Le type de fichier spécifié n'est pas autorisé par l'administrateur - NOTE ! Même si CodeMirror est activé dans la configuration, il est désactivé sur Internet Explorer car il n'est pas stable sur ce navigateur. - Remplissez l'alias et le nom de la nouvelle propriété - Il y a un problème de droits de lecture/écriture sur un fichier ou dossier spécifique - Entrez un titre - Choisissez un type - Vous allez définir une taille d'image supérieure à sa taille d'origine. Êtes-vous sûr ? + NOTE ! Même si CodeMirror est activé dans la configuration, il est désactivé dans Internet Explorer car il n'est pas suffisamment stable dans ce navigateur. + Veuillez remplir l'alias et le nom de la nouvelle propriété! + Il y a un problème de droits en lecture/écriture sur un fichier ou dossier spécifique + Erreur de chargement du script d'une Partial View (fichier : %0%) + Erreur de chargement du userControl '%0%' + Erreur de chargement d'un customControl (Assembly: %0%, Type: '%1%') + Erreur de chargement d'un script du MacroEngine (fichier : %0%) + "Erreur de parsing d'un fichier XSLT : %0% + "Erreur de lecture d'un fichier XSLT : %0% + Veuillez entrer un titre + Veuillez choisir un type + Vous allez définir une taille d'image supérieure à sa taille d'origine. Êtes-vous certain(e) de vouloir continuer? Erreur dans le script Python - Le script Python n'a pas été sauvegardé, car il contient des erreur(s) + Le script Python n'a pas été sauvegardé car il contient des erreurs Noeud de départ supprimé, contactez votre administrateur - Séléctionnez du contenu avant de changer le style + Veuillez sélectionner du contenu avant de changer le style Aucun style actif disponible - Placez le curseur à gauche des deux cellules que vous voulez fusionner - Vous ne pouvez pas scinder une cellule que vous n'avez pas fusionné. + Veuillez placer le curseur à gauche des deux cellules que vous voulez fusionner + Vous ne pouvez pas scinder une cellule qui n'a pas été fusionnée. Erreur dans le code source XSLT - Le XSLT n'a pas été sauvegardé, car il contient des erreur(s) - Il y a une erreur de configuration du type de données de cette propriété. Vérifiez ce type de données. + Le XSLT n'a pas été sauvegardé car il contient des erreurs + Il y a une erreur de configuration du type de données utilisé pour cette propriété, veuillez vérifier le type de données. A propos @@ -338,8 +419,10 @@ Actions Ajouter Alias - Êtes-vous sûr ? - Bordure + Tout + Êtes-vous certain(e)? + Retour + Bord par Annuler Marge de cellule @@ -348,13 +431,13 @@ Fermer la fenêtre Commenter Confirmer - Contraindre les proportions + Conserver les proportions Continuer Copier Créer Base de données Date - Defaut + Défaut Supprimer Supprimé Suppression... @@ -364,26 +447,29 @@ Télécharger Editer Edité - Elements + Eléments Email Erreur Trouver Hauteur Aide - Icone + Icône Importer Marge intérieure Insérer Installer + Non valide Justifier - Langage - Layout - Chargement - Fermé + Libellé + Langue + Mise en page + En cours de chargement + Bloqué Connexion Déconnexion Déconnexion Macro + Obligatoire Déplacer Plus Nom @@ -402,6 +488,7 @@ Propriétés Email de réception des données de formulaire Corbeille + Votre corbeille est vide Restant Renommer Renouveller @@ -409,12 +496,13 @@ Réessayer Permissions Rechercher + Désolé, nous ne pouvons pas trouver ce que vous recherchez Serveur Montrer Afficher la page à l'envoi Taille Trier - Submit + Envoyer Type Rechercher... Haut @@ -431,11 +519,45 @@ Oui Dossier Résultats de recherche - Reorder - I am done reordering + Réorganiser + J'ai fini de réorganiser + Prévisualiser + Modifier le mot de passe + vers + Liste + Sauvegarde... + actuel + Intégrer + sélectionné + + + Noir + Vert + Jaune + Orange + Bleu + Rouge + + + Ajouter un onglet + Ajouter une propriété + Ajouter un éditeur + Ajouter un modèle + Ajouter un noeud enfant + Ajouter un enfant + + Editer le type de données + + Parcourir les sections + + Raccourcis + afficher les raccourcis + + Passer à la vue en liste + Basculer vers l'autorisation comme racine - Background color + Couleur de fond Gras Couleur de texte Police @@ -445,131 +567,128 @@ Page - L'installeur n'a pas pu se connecter à la base de données. - Impossible de modifier le fichier web.config file. Modifiez s'il vous plait la "connection string" manuellement. - Votre base de données a été détectée et identifiée telle que - Configurtion de la base de données + Le programme d'installation ne parvient pas à se connecter à la base de données. + Impossible de sauvegarder le fichier web.config. Veuillez modifier la "connection string" manuellement. + Votre base de données a été détectée et est identifiée comme étant + Configuration de la base de données install pour installer la base de données Umbraco %0% + Appuyez sur le bouton installer pour installer la base de données Umbraco %0% ]]> - Suivant pour procéder.]]> - Base de données non trouvée ! Vérifiez les informations de la "connection string" dans le fichier web.config.

-

Pour poursuivre, éditez le fichier "web.config" (avec Visual Studio ou votre éditeur de texte favori), scrollez jusqu'en bas, ajoutez une "connection string" dans la ligne "umbracoDbDSN" et sauvegardez votre fichier.

+ Suivant pour poursuivre.]]> + Base de données non trouvée ! Veuillez vérifier les informations de la "connection string" dans le fichier web.config.

+

Pour poursuivre, veuillez éditer le fichier "web.config" (avec Visual Studio ou votre éditeur de texte favori), scroller jusqu'en bas, ajouter le "connection string" pour votre base de données dans la ligne avec la clé "umbracoDbDSN" et sauvegarder le fichier.

Cliquez sur le bouton Réessayer lorsque cela est fait.
Plus d'informations sur l'édition du fichier web.config ici.

]]>
- - Contactez votre administrateur système si nécessaire. - Si vous installez Umbraco sur votre ordinateur, vous aurez probablement besoin de consulter votre administrateur système.]]> + + Veuillez contacter votre fournisseur de services internet si nécessaire. + Si vous installez Umbraco sur un ordinateur ou un serveur local, vous aurez peut-être besoin de consulter votre administrateur système.]]> Appuyez sur le bouton Upgrader pour mettre à jour votre base de données vers Umbraco %0%

- Ne vous inquiétez pas : aucun contenu ne sera supprimé et tout continuera à fonctionner parfaitement ensuite ! + N'ayez pas d'inquiétude : aucun contenu ne sera supprimé et tout continuera à fonctionner parfaitement par après !

]]>
- Appuyez sur Suivant pour - poursuivre. ]]> + + Appuyez sur Suivant pour + poursuivre. ]]> + Suivant pour poursuivre la configuration]]> - Le mot de passe par défaut doit être changé !]]> - L'utilisateur par défaut a été désactivé ou n'a pas accès à Umbraco!

Aucune action n'est requise. Cliquez sur Suivant pour poursuivre.]]> - Le mot de passe par défaut a été modifié avec succès !

Aucune action n'est requise. Cliquez sur Suivant pour poursuivre.]]> - Le mot de passe a été changé ! - - Umbraco créer un utilisateur par défaut avec le login ('admin') et le mot de passe ('default'). - Il est important que ce mot de passe soit changé pour quelque-chose de sécurisé et unique. -

-

- Cette étape va vérifier le mot de passe par défaut et vérifier s'il est nécessaire de le changer. -

- ]]>
+ Le mot de passe par défaut doit être modifié !]]> + L'utilisateur par défaut a été désactivé ou n'a pas accès à Umbraco!

Aucune autre action n'est requise. Cliquez sur Suivant pour poursuivre.]]> + Le mot de passe par défaut a été modifié avec succès depuis l'installation!

Aucune autre action n'est requise. Cliquez sur Suivant pour poursuivre.]]> + Le mot de passe a été modifié ! + + ('admin') et le mot de passe ('default'). Il est important que ce mot de passe soit modifié en quelque-chose de sécurisé et unique. + ]]> Pour bien commencer, regardez nos vidéos d'introduction - En cliquant sur le bouton Suvant (ou en modifiant umbracoConfigurationStatus dans le fichier web.config), vous acceptez la licence de ce logiciel telle que spécifiée dans le champ ci-dessous. Remarque : cette distribution Umbraco consiste en deux licences différentes, la licence open source MIT pour le framework et la licence Umbraco freewarequi couvre l'UI. + En cliquant sur le bouton "Suivant" (ou en modifiant umbracoConfigurationStatus dans le fichier web.config), vous acceptez la licence de ce logiciel telle que spécifiée dans le champ ci-dessous. Veuillez noter que cette distribution Umbraco consiste en deux licences différentes, la licence open source MIT pour le framework et la licence Umbraco freeware qui couvre l'UI. Pas encore installé. - Fichiers et dossiers affectés - Plus d'informations sur la définition des permissions + Fichiers et dossiers concernés + Plus d'informations sur la configuration des permissions Vous devez donner à ASP.NET les droits de modification sur les fichiers/dossiers suivants - Vos permissions sont presques parfaites !

- Vous pouvez faire fonctionner Umbraco sans problèmes, mais vous ne serez pas en mesure d'installer des packages, ce qui est hautement recommandé pour tirer pleinement parti d'Umbraco.]]>
- Comment le résoudre + Vos configurations de permissions sont presque parfaites !

+ Vous pouvez faire fonctionner Umbraco sans problèmes, mais vous ne serez pas en mesure d'installer des packages, ce qui est hautement recommandé pour tirer pleinement profit d'Umbraco.]]>
+ Comment résoudre Cliquez ici pour lire la version texte - tutoriel vidéo sur la définition des permissions pour Umbraco, ou lisez la version texte.]]> - Vos permissions pourraient être problématiques ! + tutoriel vidéo sur la définition des permissions des répertoires pour Umbraco, ou lisez la version texte.]]> + Vos configurations de permissions pourraient poser problème !

- Vous pouvez faire fonctionner Umbraco sans problèmes, mais vous ne serez pas en mesure d'installer des packages, ce qui est hautement recommandé pour tirer pleinement parti d'Umbraco.]]>
- Vos permissions ne sont pas prêtes pour Umbraco ! + Vous pouvez faire fonctionner Umbraco sans problèmes, mais vous ne serez pas en mesure d'installer des packages, ce qui est hautement recommandé pour tirer pleinement profit d'Umbraco.]]> + Vos configurations de permissions ne sont pas prêtes pour Umbraco !

Pour faire fonctionner Umbraco, vous aurez besoin de mettre à jour les permissions sur les fichiers/dossiers.]]>
- Vos permissions sont parfaites !

- Vous êtes prêt à faire fonctionner Umbraco et installer des packages !]]>
+ Vos configurations de permissions sont parfaites !

+ Vous êtes prêt(e) à faire fonctionner Umbraco et à installer des packages !]]>
Résoudre un problème sur un dossier - Suivz ce lien pour plus d'informations sur ASP.NET et la création de dossiers + Suivez ce lien pour plus d'informations sur les problèmes avec ASP.NET et la création de dossiers Définir les permissions de dossier Je veux démarrer "from scratch" - + Apprenez comment) - Vous pouvez toujours choisir d'installer Runway plus tard. Pour cela allez dans la séction "Développeur" et séléctionnez "Packages". + Vous pouvez toujours choisir d'installer Runway plus tard. Pour cela, allez dans la section "Développeur" et sélectionnez "Packages". ]]> - Vous avez mis en oeuvre une plateforme Umbraco clean. Que voulez-vous faire ensuite ? + Vous venez de mettre en place une plateforme Umbraco toute nette. Que voulez-vous faire ensuite ? Runway est installé + Les fondations en place. Choisissez les modules que vous souhaitez installer par-dessus
Voici la liste des modules recommandés, cochez ceux que vous souhaitez installer, ou regardez la liste complète des modules ]]>
Recommandé uniquement pour les utilisateurs expérimentés Je veux commencer avec un site simple - + - "Runway" est un simple site fournissant des types de documents et modèles basiques. L'installeur peut mettre en oeuvre Runway automatiquement pour vous, - mais vous pouvez facilement léditer, lenrichir, ou le supprimer ensuite. Il n'est pas nécessaire et vous pouvez parfaitement utiliser Umbraco sans. Pour autant, - Runway offre un socle facile basé sur des bonnes pratiques pour vous permettre de commencer rapidement. - Si vous choisissez d'installer Runway, vous pouvez, de manière optionnelle, choisir des blocks appelés Runway Modules pour enrichir les pages du site. + "Runway" est un site simple qui fournit des types de documents et des modèles de base. L'installateur peut mettre en place Runway automatiquement pour vous, + mais vous pouvez facilement l'éditer, l'enrichir, ou le supprimer par la suite. Il n'est pas nécessaire, et vous pouvez parfaitement vous en passer pour utiliser Umbraco. Cela étant dit, + Runway offre une base facile, fondée sur des bonnes pratiques, pour vous permettre de commencer plus rapidement que jamais. + Si vous choisissez d'installer Runway, vous pouvez sélectionner en option des blocs de base, appelés Runway Modules, pour enrichir les pages de votre site.

Inclus avec Runway : Home page, Getting Started page, Installing Modules page.
Modules optionnels : Top Navigation, Sitemap, Contact, Gallery.
]]>
- Qu'est ce que Runway - Step 1/5 : Licence - Step 2/5 : Configuration base de données - Step 3/5 : Validation des permissions de fichiers - Step 4/5 : Sécurité Umbraco - Step 5/5 : Umbraco est prêt + Qu'est-ce que Runway + Etape 1/5 : Accepter la licence + Etape 2/5 : Configuration de la base de données + Etape 3/5 : Validation des permissions de fichiers + Etape 4/5 : Sécurité Umbraco + Etape 5/5 : Umbraco est prêt Merci d'avoir choisi Umbraco Parcourir votre nouveau site -Vous avez installé Runway, alors pourquoi pas jeter un oeil au look de votre nouveau site ?]]> - Aide et information -Obtenez de l'aide de notre award winning communauté, parcourez la documentation our regardez quelques vidéos sur "Comemnt construire un site simple", "Comment utiliser les packages" et un guide rapide sur la terminologie Umbraco]]> +Vous avez installé Runway, alors pourquoi ne pas jeter un oeil au look de votre nouveau site ?]]>
+ Aide et informations complémentaires +Obtenez de l'aide de notre "award winning" communauté, parcourez la documentation ou regardez quelques vidéos gratuites sur la manière de construire un site simple, d'utiliser les packages ainsi qu'un guide rapide sur la terminologie Umbraco]]> Umbraco %0% est installé et prêt à être utilisé - /web.config file et mettre à jour le paramètre AppSetting dans umbracoConfigurationStatus en bas de la valeur de '%0%'.]]> - démarrer maintenant en cliquant sur le bouton "Lancer Umbraco" ci-dessous.
-Si vous débutez sur Umbraco, vous pouvez trouver plein de ressources sur nos pages "Getting Started".]]>
+ fichier /web.config et mettre à jour le paramètre AppSetting umbracoConfigurationStatus situé en bas à la valeur '%0%'.]]> + démarrer instantanément en cliquant sur le bouton "Lancer Umbraco" ci-dessous.
+Si vous débutez avec Umbraco, vous pouvez trouver une foule de ressources dans nos pages "Getting Started".]]>
Lancer Umbraco -Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à ajouter du contenu, mettez à jour les templates et feuilles de styles ou ajoutez ds nouvelles fonctionnalités]]> +Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à ajouter du contenu, à mettre à jour les modèles d'affichage et feuilles de styles ou à ajouter de nouvelles fonctionnalités]]> La connexion à la base de données a échoué. Umbraco Version 3 Umbraco Version 4 Regarder - umbraco %0%, qu'il s'agisse d'une installation récente ou de la version 3.0 + Umbraco %0%, qu'il s'agisse d'une nouvelle installation ou d'une mise à jour à partir de la version 3.0

- Appuyez sur Press "suivant" pour commencer.]]>
+ Appuyez sur "suivant" pour commencer l'assistant.]]>
Code de la Culture Nom de la culture - Vous avez été inactif, la déconnexion automatique se fera dans + Vous avez été inactif et la déconnexion aura lieu automatiquement dans Renouvellez votre session maintenant pour sauvegarder votre travail @@ -581,8 +700,20 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Joyeux vendredi Joyeux samedi Connectez-vous ci-dessous - La session a expiré - © 2001 - %0%
umbraco.com

]]>
+ Identifiez-vous avec + La session a expiré + © 2001 - %0%
Umbraco.com

]]>
+ Mot de passe oublié? + Un email contenant un lien pour ré-initialiser votre mot de passe sera envoyé à l'adresse spécifiée + Un email contenant les instructions de ré-initialisation de votre mot de passe sera envoyée à l'adresse spécifiée si elle correspond à nos informations. + Revenir au formulaire de connexion + Veuillez fournir un nouveau mot de passe + Votre mot de passe a été mis à jour + Le lien sur lequel vous avez cliqué est non valide ou a expiré. + Umbraco: Ré-initialiser le mot de passe + + Votre nom d'utilisateur pour vous connecter au back-office Umbraco est : %0%.

Cliquez ici pour ré-initialiser votre mot de passe, ou recopiez cet URL dans votre navigateur :

%1%

]]> +
Tableau de bord @@ -590,21 +721,21 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Contenu - Choisissez une page ci-dessous... + Choisissez la page au-dessus... %0% a été copié dans %1% - Choisissez, ci-dessous, où le document %0% doit être copié + Choisissez ci-dessous l'endroit où le document %0% doit être copié %0% a été déplacé dans %1% - Choisissez, ci-dessous, où le document %0% doit être déplacé - a été choisi comme racine de votre contenu, cliquez sur 'ok' ci-dessous. - Aucun noeud choisi, choisissez s'il vous plait un noeud dans la liste ci-dessus avant de cliquer sur 'ok'. - Le noeud actuel n'est pas autorisé dans le noeud choisi à cause de son type + Choisissez ci-dessous l'endroit où le document %0% doit être déplacé + a été choisi comme racine de votre nouveau contenu, cliquez sur 'ok' ci-dessous. + Aucun noeud n'a encore été choisi, veuillez choisir un noeud dans la liste ci-dessus avant de cliquer sur 'ok'. + Le noeud actuel n'est pas autorisé sous le noeud choisi à cause de son type Le noeud actuel ne peut pas être déplacé dans une de ses propres sous-pages Le noeud actuel ne peut pas exister à la racine - L'action n'est pas autorisée car vous n'avez pas les droits sur un ou plus des noeuds enfants. - Relier les items copiés à l'original + L'action n'est pas autorisée car vous n'avez pas les droits suffisants sur un ou plusieurs noeuds enfants. + Relier les éléments copiés à l'original - Editer vos notifications pour %0% + Editez vos notifications pour %0% - Hi %0%

+ + Hello %0%

Ceci est un email automatique pour vous informer que la tâche '%1%' a été executée sur la page '%2%' @@ -626,11 +758,11 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à

-

Update summary:

+

Résumé de la mise à jour :

%6%
@@ -638,71 +770,79 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à

Bonne journée !

- Le Robot Umbraco vous salue + Avec les salutations du Robot Umbraco

]]>
La notification [%0%] à propos de %1% a été executée sur %2% Notifications - localisez le package. Les packages Umbraco ont généralement une extension .umb ou .zip. + Choisissez un package sur votre ordinateur en cliquant sur le bouton Parcourir
+ et localisez le package. Les packages Umbraco ont généralement une extension ".umb" ou ".zip". ]]>
Auteur - Demon + Démo Documentation Meta data du package Nom du package - Le package ne contient aucun éléments -
- Vous pouvez supprimer tranquillement le package de votre installation en cliquant sur "Désinstaller" ci-dessous.]]>
- Aucune mises à jour disponibles - Options de package + Le package ne contient aucun élément +
+ Vous pouvez supprimer tranquillement ce package de votre installation en cliquant sur "Désinstaller le package" ci-dessous.]]>
+ Aucune mise à jour disponible + Options du package Package readme - Package repository + Repository des packages Confirmation de désinstallation - Package was uninstalled + Le package a été désinstallé Le package a été désinstallé avec succès - Désintaller le package - - Remarque : tous les documents, media etc dépendants des éléments que vous supprimerez, arrêteront de fonctionner, ce qui peut provoquer une instabilité du système, - désinstallez avec prudence. En cas de doute, contactez l'auteur du package.]]> + Désinstaller le package + + Remarque : tous les documents, media etc. dépendant des éléments que vous supprimez vont cesser de fonctionner, ce qui peut provoquer une instabilité du système, + désinstallez donc avec prudence. En cas de doute, contactez l'auteur du package.]]> Télécharger la mise à jour depuis le repository Mettre à jour le package Instructions de mise à jour - Il y a une mise à jour disponible pour ce package. Vous pouvez la télécharger directement depuis le repository. - Version de package - Historique des version de package + Il y a une mise à jour disponible pour ce package. Vous pouvez la télécharger directement depuis le repository des packages Umbraco. + Version du package + Historique des versions du package Voir le site internet du package + Package déjà installé + Ce package ne peut pas être installé, il nécessite au minimum la version Umbraco %0% + Désinstallation... + Téléchargement... + Import... + Installation... + Redémarrage, veuillez patienter... + Terminé, votre navigateur va être rafraîchi, veuillez patienter... Coller en conservant le formatage (non recommandé) - Le texte que vous tentez de coller contient des caractères spéciaux ou de formatage. Cela peut être dû à une copie d'un texte de Microsoft Word. Umbraco peut supprimer les caractères spéciaux et le formatage automatiquement, de manière à ce que le texte collé soit plus utilisable pour le Web. - Coller en tant que texte brut sans formatage - Coller, mais supprimer le formatage (recommendé) + Le texte que vous tentez de coller contient des caractères spéciaux ou du formatage. Cela peut être dû à une copie d'un texte depuis Microsoft Word. Umbraco peut supprimer automatiquement les caractères spéciaux et le formatage, de manière à ce que le texte collé convienne mieux pour le Web. + Coller en tant que texte brut sans aucun formatage + Coller, mais supprimer le formatage (recommandé) Protection basée sur les rôles via les groupes de membres Umbraco.]]> - l'authentification basée sur les rôles.]]> + l'authentification basée sur les rôles.]]> Page d'erreur - Utilisé quand les gens sont connectés, mais n'ont pas accès + Utilisé pour les personnes connectées, mais qui n'ont pas accès Choisissez comment restreindre l'accès à cette page %0% est maintenant protégée Protection supprimée de %0% Page de connexion - Choisissez la page qui a le formulaire de login + Choisissez la page qui contient le formulaire de connexion Supprimer la protection - Choisissez la page qui contient le formulaire de login et les messages d'erreur - Piochez les roles qui ont accès à cette page - Définissez l'identifiant et mot de passe pour cette page + Choisissez les pages qui contiennent le formulaire de connexion et les messages d'erreur + Choisissez les rôles qui ont accès à cette page + Définissez l'identifiant et le mot de passe pour cette page Protection utilisateur unique - Si vous souhaitez simplement mettre en place une protection par identifiant et mot de passe + Si vous souhaitez mettre en place une protection simple utilisant un identifiant et un mot de passe uniques @@ -710,44 +850,47 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à %0% n'a pas pu être publié car cet élément est programmé pour être publié bientôt. ]]> + - Inclure les pages enfants non publiées + Inclure les pages enfant non publiées Publication en cours - veuillez patienter... - %0% sur %1% des pages ont été publiées... + %0% pages sur %1% ont été publiées... %0% a été publié %0% et ses pages enfants ont été publiées - Publier %0% et ses pages enfants - ok pour publier %0% et le rendre ainsi accessible publiquement.

- Vous pouvez publier cette page et ses sous pages en cochant publier tous les enfants ci-dessous. + Publier %0% et toutes ses pages enfant + Publier pour publier %0% et la rendre ainsi accessible publiquement.

+ Vous pouvez publier cette page et toutes ses sous-pages en cochant Inclure les pages enfant non pubiées ci-dessous. ]]>
- Vous n'avez configuré aucune couleurs approuvées + Vous n'avez configuré aucune couleur approuvée - Ajouter un lien externe - Ajouter un lien interne - Ajouter + introduire un lien externe + choisir une page interne Légende - Page interne - URL - Descendre - Monter + Lien Ouvrir dans une nouvelle fenêtre - Supprimer le lien + introduisez la légende à afficher + Introduiser le lien + + + Réinitialiser Version actuelle - Le texte en Rouge signifit qu'il a été supprimé de la version choisie, vert signifie ajouté]]> - Le document est passé à une version antérieure + Le texte en Rouge signifie qu'il a été supprimé de la version choisie, vert signifie ajouté]]> + Le document a été restauré à une version antérieure Ceci affiche la version choisie en tant que HTML, si vous souhaitez voir les différences entre les deux versions en même temps, utilisez la vue différentielle Revenir à Choisissez une version @@ -765,16 +908,24 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Medias Membres Newsletters - Paramètres + Configuration Statistiques Traduction Utilisateurs Aide + Formulaires + Analytics + + + aller à + Rubriques d'aided pour + Chapitres vidéo pour + Les meilleurs tutoriels vidéo Umbraco - Template par défaut + Modèle par défaut Clé de dictionnaire - Pour importer un type de document, trouvez le fichier ".udt" sur votre ordinateur en cliquant sur le bouton "Parcourir" et cliquez sur "Importer" (une confirmation vous sera demandé à l'écran d'après) + Pour importer un type de document, trouvez le fichier ".udt" sur votre ordinateur en cliquant sur le bouton "Parcourir" et cliquez sur "Importer" (une confirmation vous sera demandée à l'écran suivant) Titre du nouvel onglet Type de noeud Type @@ -784,22 +935,29 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Onglet Titre de l'onglet Onglets - Type de contenu master activé + Type de contenu de base activé Ce type de contenu utilise - en tant que type de contenu master, les onglets du type de contenu master ne sont pas affichés et peuvent seulement être modifiés dans le type de contenu master lui-même. - Aucune propriétés définies dans cet onglet. Cliquez sur le lien "Ajouter une nouvelle propriété" en haut pour créer une nouvelle propriété. - Type de contenu parent - Créer le template correspondant - + en tant que type de contenu de base. Les onglets du type de contenu de base ne sont pas affichés et peuvent seulement être modifiés à partir du type de contenu de base lui-même. + Aucune propriété définie dans cet onglet. Cliquez sur le lien "Ajouter une nouvelle propriété" en-haut pour créer une nouvelle propriété. + Type de contenu de base + Créer le modèle correspondant + Ajouter une icône + - Sort order - Creation date + Ordre de tri + Date de création Tri achevé. - Faites glisser les différents éléments ci-dessous vers le haut ou le bas pour définir comment ils doivent être triés. Ou cliquez sur les entêtes de colonne pour trier la collection complète d'éléments + Faites glisser les différents éléments vers le haut ou vers le bas pour définir la manière dont ils doivent être organisés. Ou cliquez sur les entêtes de colonnes pour trier la collection complète d'éléments
Ne fermez pas cette fenêtre durant le tri.]]>
- La publication a été annulée par un extension tierce. + Validation + Les erreurs de validation doivent être corrigées avant de pouvoir sauvegarder l'élément + Echec + Permissions utilisateur insuffisantes, l'opération n'a pas pu être complétée + Annulation + L'opération a été annulée par une extension tierce + La publication a été annulée par une extension tierce. Le type de propriété existe déjà Type de propriété créé Type de données : %1%]]> @@ -807,33 +965,35 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Type de documet sauvegardé Onglet créé Onglet supprimé - Onglet d'ID : %0% supprimé + Onglet avec l'ID : %0% supprimé Feuille de style non sauvegardée Feuille de style sauvegardée Feuille de style sauvegardée sans erreurs - Type de données sauvegardée - Element de dictionnaire sauvegardé + Type de données sauvegardé + Elément de dictionnaire sauvegardé La publication a échoué car la page parent n'est pas publiée Contenu publié et visible sur le site - Content sauvegardé - N'oubliez pas de publier pour rendre les changements visibles + Contenu sauvegardé + N'oubliez pas de publier pour rendre les modifications visibles Envoyer pour approbation - Les changements ont été envoyés pour approbation + Les modifications ont été envoyées pour approbation Media sauvegardé Media sauvegardé sans erreurs Membre sauvegardé - Propriété de feuille de style sauvegardé - Feuille de style sauvegardé - Template sauvegardé - Erreur lors de la sauvegarde de l'utilisateur + Propriété de feuille de style sauvegardée + Feuille de style sauvegardée + Modèle sauvegardé + Erreur lors de la sauvegarde de l'utilisateur (consultez les logs) Utilisateur sauvegardé Type d'utilisateur sauvegardé Fichier non sauvegardé Le fichier n'a pas pu être sauvegardé. Vérifiez les permissions de fichier. Fichier sauvegardé Fichier sauvegardé sans erreurs - Langage sauvegardé + Langue sauvegardée + Type de média sauvegardé + Type de membre sauvegardé Le script Python n'a pas été sauvegardé Le script Python n'a pas été sauvegardé à cause d'erreurs Le script Python a été sauvegardé @@ -846,30 +1006,35 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Le XSLT contenait une erreur Le XSLT n'a pas pu être sauvegardé, vérifiez les permissions de fichier Le XSLT a été sauvegardé - Aucune erreurs dans le XSLT + Aucune erreur dans le XSLT Contenu publié Vue partielle sauvegardée Vue partielle sauvegardée sans erreurs ! Vue partielle non sauvegardée Une erreur est survenue lors de la sauvegarde du fichier. - + Vue script sauvegardée + Vue script sauvegardée sans erreur ! + Vue script non sauvegardée + Une erreur est survenue lors de la sauvegarde du fichier. + Une erreur est survenue lors de la sauvegarde du fichier. + - Utilise la synthaxe CSS. Ex : h1, .redHeader, .blueTex + Utilise la syntaxe CSS. Ex : h1, .redHeader, .blueTex Editer la feuille de style Editer la propriété de feuille de style - Nommer pour identifier la propriété dans le Rich Text Editor + Donner un nom pour identifier la propriété dans le Rich Text Editor Prévisualiser Styles - Editer le modèle template + Editer le modèle Insérer une zone de contenu Insérer un placeholder de zone de contenu Insérer un élément de dictionnaire - Insert Macro - Insert umbraco page field - Modèle master - Guide rapide aux tags des modèles Umbraco + Insérer une Macro + Insérer un champ de la page Umbraco + Modèle de base + Guide rapide concernant les tags des modèles Umbraco Modèle @@ -877,40 +1042,113 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Choisissez une mise en page Ajouter une ligne Ajouter du contenu - Contenu goutte + Supprimer le contenu Paramètres appliqués - Ce contenu est pas autorisée ici - Ce contenu est permis ici + Ce contenu n'est pas autorisé ici + Ce contenu est autorisé ici Cliquez pour intégrer - Cliquez pour insérer l'image + Cliquez pour insérer une image Légende de l'image... - Ecrire ici... + Ecrivez ici... - Layouts Grid - Layouts sont la superficie totale de travail pour l'éditeur de grille, en général, vous avez seulement besoin d'une ou deux configurations différentes - Ajouter Grid Layout - Ajustez la mise en page en définissant la largeur des colonnes et ajouter des sections supplémentaires - Configurations des lignes - Les lignes sont des cellules prédéfinies disposées horizontalement - Ajouter une configuration de la ligne - Ajustez la ligne en réglant la largeur des cellules et en ajoutant des cellules supplémentaires + Mises en pages de la Grid + Les mises en pages représentent la surface de travail globale pour l'éditeur de grille, en général, vous n'avez seulement besoin que d'une ou deux mises en pages différentes + Ajouter une mise en page de grille + Ajustez la mise en page en définissant la largeur des colonnes et en ajoutant des sections supplémentaires + Configurations des rangées + Les rangées sont des cellules prédéfinies disposées horizontalement + Ajouter une configuration de rangée + Ajustez la rangée en réglant la largeur des cellules et en ajoutant des cellules supplémentaires Colonnes Nombre total combiné de colonnes dans la configuration de la grille Paramètres - Configurer quels paramètres éditeurs peuvent changer + Configurez les paramètres qui peuvent être modifiés par les éditeurs - Modes - Configurer ce style éditeurs peuvent changer + + Styles + Configurez les effets de style qui peuvent être modifiés par les éditeurs - Les réglages seulement économiser si la configuration du json saisi est valide + Les paramètres ne seront sauvegardés que si la configuration json saisie est valide Autoriser tous les éditeurs - Autoriser toutes les configurations de lignes + Autoriser toutes les configurations de rangées + Configurer comme défaut + Choisir en plus + Choisir le défaut + ont été ajoutés + + + Compositions + Vous n'avez pas ajouté d'onglet + Ajouter un nouvel onglet + Ajouter un autre onglet + Hérité de + Ajouter une propriété + Label requis + + Activer la vue en liste + Configure l'élément de contenu de manière à afficher ses éléments enfants sous forme d'une liste que l'on peut trier et filtrer, les enfants ne seront pas affichés dans l'arborescence + + Modèles autorisés + Sélectionnez les modèles que les éditeurs sont autorisés à utiliser pour du contenu de ce type. + Autorisé comme racine + Autorisez les éditeurs à créer du contenu de ce type à la racine de l'arborescence de contenu. + Oui - autoriser du contenu de ce type à la racine + + Types de noeuds enfants autorisés + Autorisez la création de contenu des types spécifiés sous le contenu de ce type-ci + + Choisissez les noeuds enfants + Hériter des onglets et propriétés d'un type de document existant. De nouveaux onglets seront ajoutés au type de document actuel, ou fusionnés s'il existe un onglet avec un nom sililaire. + Ce type de contenu est utilisé dans une composition, et ne peut donc pas être lui-même un composé. + Il n'y a pas de type de contenu disponible à utiliser dans une composition. + + Editeurs disponibles + Réutiliser + Configuration de l'éditeur + + Configuration + + Oui, supprimer + + a été déplacé en-dessous + a été copié en-dessous + Sélectionnez le répertoire à déplacer + Sélectionnez le répertoire à copier + dans l'arborescence ci-dessous + + Tous les types de document + Tous les documents + Tous les éléments media + + utilisant ce type de document seront supprimés définitivement, veuillez confirmer que vous souhaitez les supprimer également. + utilisant ce type de media seront supprimés définitivement, veuillez confirmer que vous souhaitez les supprimer également. + utilisant ce type de membre seront supprimés définitivement, veuillez confirmer que vous souhaitez les supprimer également + + et tous les documents utilisant ce type + et tous les éléments media utilisant ce type + et tous les membres utilisant ce type + + utilisant cet éditeur seront mis à jour avec la nouvelle configuration + + Le membre peut éditer + Afficher dans le profil du membre + l'onglet n'a pas d'ordonnancement + + + + Création des modèles + ceci peut prendre un certain temps, ne vous inquiétez pas + Les modèles ont été générés + Les modèles n'ont pas pu être générés + La génération des modèles a échoué, veuillez consulter les erreurs dans le log Umbraco + + Champ alternatif Texte alternatif @@ -924,67 +1162,68 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Formater comme une date Encoder en HTML Remplacera les caractères spéciaux par leur équivalent HTML. - Sera insérer après la valeur du champ - Sera inséré après la valeur du champ + Sera inséré après la valeur du champ + Sera inséré avant la valeur du champ Minuscules Aucun - Inserer après le champ - Inserer après le champ - Recursif - Supprimer les balises paragraphes - Supprimera tous les &lt;P&gt; + Insérer après le champ + Insérer avant le champ + Récursif + Supprimer les balises de paragraphes + Supprimera toute balise &lt;P&gt; au début et à la fin du texte Champs standards Majuscules - Encode en URL - Formatera les caractères spéciaux de manière à ce qu'ils soient utilisés dans une URL - Sera seulement utilisé quand toutes les valeurs ci-dessous seront vides - Ce champ sera utilisé seulement si le champ primaire est vide - Yes, with time. Separator: + Encode pour URL + Formatera les caractères spéciaux de manière à ce qu'ils soient reconnus dans une URL + Sera seulement utilisé si toutes les valeurs des champs ci-dessus sont vides + Ce champ sera utilisé seulement si le champ initial est vide + Oui, avec l'heure. Séparateur: Tâches qui vous sont assignées - vous sont assignées. Pour voir une vue détaillée incluant les commentaires, cliquez sur "Details" ou juste le nom de la page. - Vous pouvez télécharger la format au format XML en cliquant sur le lien "Télécharger XML".
- Pour terminer une tâche de traduction, allez sur Details, puis cliquer sur le bouton "Terminer tâche". + vous sont assignées. Pour voir un aperçu détaillé incluant les commentaires, cliquez sur "Détails" ou juste sur le nom de la page. + Vous pouvez aussi télécharger la page au format XML en cliquant sur le lien "Télécharger XML".
+ Pour clôturer une tâche de traduction, allez sur Détails, puis cliquez sur le bouton "Terminer la tâche". ]]>
- Terminer tâche + Terminer la tâche Détails - Télécharger toutes les traductions au format XML + Télécharger toutes les tâches de traductions au format XML Télécharger XML Télécharger la DTD XML Champs Inclure les pages enfants [%0%] tâches de traductions pour %1% - Aucun utilisateurs traducteurs trouvés. Vous devez créer un utilisateur traducteur avant d'envoyer votre contenu pour traduction - Tâches que vous avez créé - créées par vous. Pour voir une vue détaillée incluant les commentaires, - cliquez sur "Détails" ou juste le nom de la page. Vous pouvez aussi télécharger la page au format XML en cliquant sur le lien "Télécharger XML". - Pour terminer une tâche de traduction, allez sur Details, puis cliquer sur le bouton "Terminer tâche". + Aucun utilisateur traducteur trouvé. Veuillez créer un utilisateur traducteur avant d'envoyer du contenu pour traduction + Tâches que vous avez créées + que vous avez créées. Pour voir un aperçu détaillé incluant les commentaires, + cliquez sur "Détails" ou juste sur le nom de la page. Vous pouvez aussi télécharger la page au format XML en cliquant sur le lien "Télécharger XML". + Pour clôturer une tâche de traduction, allez sur Détails, puis cliquez sur le bouton "Terminer tâche". ]]> - La page '%0%' a été envoyé pour traduction + La page '%0%' a été envoyée pour traduction + Veuillez choisir la langue dans laquelle le contenu doit être traduit Envoyer la page '%0%' pour traduction Assignée par - Tâches ouvertures + Tâches ouvertes Nombre total de mots Traduire en Traduction complétée. - Vous pouvez prévisualiser les pages que vous avez traduites, en cliquant ci-dessous. Si la page originale est trouvée, vous aurez la comparaison entre les deux pages. - Traductio échouée, il semble que le fichier XML soit corrompu + Vous pouvez prévisualiser les pages que vous avez traduites en cliquant ci-dessous. Si la page originale est trouvée, vous verrez une comparaison entre les deux pages. + Traduction échouée, il se pourrait que fichier XML soit corrompu Options de traduction Traducteur Uploader le fichier de traduction XML @@ -993,20 +1232,21 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Navigateur de cache Corbeille Packages créés - Typesde données + Types de données Dictionnaire Packages installés - Installer un skin + Installer une skin Installer un starter kit - Langages + Langues Installer un package local Macros - Types de médias + Types de média Membres Groupes de membres Rôles Types de membres Types de documents + Types de relations Packages Packages Fichiers Python @@ -1021,53 +1261,184 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Permissions utilisateur Types d'utilisateurs Utilisateurs + Analytique - Nouvelle mise à jour prête - %0% est prêt, cliquez ici pour télécharger + Nouvelle mise à jour disponible + %0% est disponible, cliquez ici pour télécharger Aucune connexion au serveur - Erreur lors de la recherche de mises à jour. Vérifiez la stack trace pour obtenir plus d'informations sur l'erreur. + Erreur lors de la recherche de mises à jour. Veuillez vérifier le stack trace pour obtenir plus d'informations sur l'erreur. Administrateur Champ catégorie Changer le mot de passe - Changez votre mot de passe + Nouveau mot de passe Confirmez votre nouveau mot de passe - Vous pouvez changer votre mot de passe d'accès à Umbraco en remplissant le formulaire ci-dessous puis en cliquant sur le bouton "Changer le mot de passe" + Vous pouvez changer votre mot de passe d'accès au Back Office Umbraco en remplissant le formulaire ci-dessous puis en cliquant sur le bouton "Changer le mot de passe" Canal de contenu Champ description Désactiver l'utilisateur Type de document Editeur Champ extrait - Langage + Langue Identifiant - Noeud de départ dans la librarie de médias + Noeud de départ dans la librarie de média Sections Désactiver l'accès Umbraco + Ancien mot de passe Mot de passe Réinitialiser le mot de passe - Your password has been changed! - Confirmez s'il vous plait votre nouveau mot de passe - Entrez votre nouveau mot de passe + Votre mot de passe a été modifié! + Veuillez confirmer votre nouveau mot de passe + Introduisez votre nouveau mot de passe Votre nouveau mot de passe ne peut être vide ! Mot de passe actuel Mot de passe actuel invalide - Il y avait une différence entre le nouveau mot de passe et le mot de passe confirmé. Veuillez réessayer - Le mot de passe confirmé ne match pas le nouveau mot de passe saisi - Remplacer les permissions des noeuds enfants - Vous modifiez actuellement les permissions pour les pages : - Choisissez les pages pour lesquelles modifier les permissions + Il y a une différence entre le nouveau mot de passe et le mot de passe confirmé. Veuillez réessayer. + Le mot de passe confirmé ne correspond pas au nouveau mot de passe saisi! + Remplacer les permissions sur les noeuds enfants + Vous êtes en train de modifiez les permissions pour les pages : + Choisissez les pages dont les permissions doivent être modifiées Rechercher tous les enfants Noeud de départ du contenu Nom d'utilisateur Permissions utilisateur Type d'utilisateur - Types d'utilisateur + Types d'utilisateurs Rédacteur + Traducteur + Modifier Votre profil Votre historique récent La session expire dans + + Validation + Valider comme email + Valider comme nombre + Valider comme Url + ...ou introduisez une validation spécifique + Champ obligatoire + + + + La valeur est égale à la valeur recommandée : '%0%'. + La valeur du XPath '%2%' a été fixée à '%1%' dans le fichier de configuration '%3%'. + La valeur attendue pour '%2%' dans le fichier de configuration '%3%' est '%1%', mais la valeur trouvée est '%0%'. + La valeur inattendue '%0%' a été trouvée pour '%2%' dans le fichier de configuration '%3%'. + + + Custom errors est fixé à la valeur '%0%'. + Custom errors est pour la moment fixé à la valeur '%0%'. Il est recommandé de le fixer la valeur à '%1%' avant la mise en ligne. + Custom errors a été rectifié avec succès à la valeur '%0%'. + + MacroErrors est fixé à la valeur '%0%'. + MacroErrors est fixé à la valeur '%0%', ce qui empêchera certaines ou même toutes les pages de votre site de se charger complètement en cas d'erreur dans les macros. La rectification de ceci fixera la valeur à '%1%'. + MacroErrors est maintenant fixé à la valeur '%0%'. + + + Try Skip IIS Custom Errors est fixé à la valeur '%0%' et vous utilisez IIS version '%1%'. + Try Skip IIS Custom Errors est actuellement fixé à '%0%'. Il est recommandé de fixer la valeur à '%1%' pour votre version IIS (%2%). + Try Skip IIS Custom Errors a été rectifié avec succès à la valeur '%0%'. + + + Le fichier n'existe pas : '%0%'. + '%0%' dans le fichier config '%1%'.]]> + Une erreur est survenue, consultez le log pour voir l'erreur complète : %0%. + + Total XML : %0%, Total : %1% + Total XML : %0%, Total : %1% + Total XML : %0%, Total publié : %1% + + Erreur de validation du certificat : '%0%' + Erreur en essayant de contacter l'URL %0% - '%1%' + Vous êtes actuellement %0% à voir le site via le schéma HTTPS. + La valeur appSetting 'umbracoUseSSL' est fixée à 'false' dans votre fichier web.config. Une fois que vous donnerez accès à ce site en utilisant le schéma HTTPS, cette valeur devra être mise à 'true'. + La valeur appSetting 'umbracoUseSSL' est fixée à '%0%' dans votre fichier web.config, vos cookies sont %1% marqués comme étant sécurisés. + Impossible de mettre à jour la configuration 'umbracoUseSSL' dans votre fichier web.config. Erreur : %0% + + + Activer HTTPS + Fixe la configuration 'umbracoSSL' à 'true' dans la section appSettings du fichier web.config. + La configuration appSetting 'umbracoUseSSL' est maintenant fixée à 'true' dans votre fichier web.config, vos cookies seront marqués comme étant sécurisés. + + Corriger + Impossible de corriger une vérification avec un type de comparaison 'ShouldNotEqual'. + Impossible de corriger une vérification avec un type de comparaison 'ShouldEqual' avec une valeur spécifiée. + La valeur de correction n'est pas fournie. + + Le mode de compilation Debug est désactivé. + Le mode de compilation Debug a été désactivé avec succès. + Le mode de compilation Debug est actuellement activé. Il est recommandé de désactiver ce paramètre avant la mise en ligne. + + Le mode tracing est désactivé. + Le mode tracing est actuellement activé. Il est recommandé de désactiver cette configuration avant la mise en ligne. + Le mode tracing a été désactivé avec succès. + + Tous les répertoires ont les configurations de permissions adéquates. + + %0%.]]> + %0%. Aucune action n'est requise s'il n'y a pas de nécessité d'y écrire.]]> + + Tous les fichiers ont les configurations de permissions adéquates. + + %0%.]]> + %0%. Aucune action n'est requise s'il n'y a pas de nécessité d'y écrire.]]> + + X-Frame-Options, utilisé pour contrôler si un site peut être intégré dans un autre via IFRAME, a été trouvé.]]> + X-Frame-Options , utilisé pour contrôler si un site peut être intégré dans un autre via IFRAME, n'a pas été trouvé.]]> + Configurez le Header dans le fichier Config + Ajoute une valeur dans la section httpProtocol/customHeaders du fichier web.config afin d'éviter que le site ne soit intégré dans d'autres sites via IFRAME. + Une configuration générant un header qui empêche l'intégration du site par d'autres sites via IFRAME a été ajoutée à votre fichier web.config. + Impossible de modifier le fichier web.config. Erreur : %0% + + + %0%.]]> + Aucun header révélant des informations à propos de la technologie du site web n'a été trouvé. + + La section system.net/mailsettings n'a pas pu être trouvée dans le fichier Web.config. + Dans la section system.net/mailsettings du fichier Web.config, le "host" n'est pas configuré. + La configuration SMTP est correcte et le service fonctionne comme prévu. + Le serveur SMTP configuré avec le host '%0%' et le port '%1%' n'a pas pu être contacté. Veuillez vérifier et vous assurer que la configuration SMTP est correcte dans la section system.net/mailsettings du fichier Web.config. + + %0%.]]> + %0%.]]> + + + Désactiver URL tracker + Activer URL tracker + URL original + Redirigé Vers + Aucune redirection n'a été créée + Lorsqu'une page publiée est renommée ou déplacée, une redirection sera automatiquement créée vers la nouvelle page. + Supprimer + Etes-vous certain(e) de vouloir supprimer la redirection de '%0%' vers '%1%'? + Redirection d'URL supprimée. + Erreur lors de la suppression de la redirection d'URL. + Etes-vous certain(e) de vouloir désactiver le URL tracker? + URL tracker est maintenant désactivé. + Erreur lors de la désactivation de l'URL tracker, plus d'information disponible dans votre fichier log. + URL tracker est maintenant activé. + Erreur lors de l'activation de l'URL tracker, plus d'information disponible dans votre fichier log. + diff --git a/src/Umbraco.Web.UI/umbraco/create/User.ascx b/src/Umbraco.Web.UI/umbraco/create/User.ascx index 7fe65fe391..3f6ee81cbb 100644 --- a/src/Umbraco.Web.UI/umbraco/create/User.ascx +++ b/src/Umbraco.Web.UI/umbraco/create/User.ascx @@ -22,7 +22,7 @@ ControlToValidate="Email" ValidateEmptyText="false" OnServerValidate="EmailExistsCheck">
diff --git a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs index 2de10c1062..cf85e65f71 100644 --- a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs @@ -86,11 +86,16 @@ namespace Umbraco.Web var instructions = batch.SelectMany(x => x.Instructions).ToArray(); batch.Clear(); - if (instructions.Length == 0) return; - WriteInstructions(instructions); + + //Write the instructions but only create JSON blobs with a max instruction count equal to MaxProcessingInstructionCount + foreach (var instructionsBatch in instructions.InGroupsOf(Options.MaxProcessingInstructionCount)) + { + WriteInstructions(instructionsBatch); + } + } - private void WriteInstructions(RefreshInstruction[] instructions) + private void WriteInstructions(IEnumerable instructions) { var dto = new CacheInstructionDto { @@ -138,9 +143,18 @@ namespace Umbraco.Web // batch if we can, else write to DB immediately if (batch == null) - WriteInstructions(instructions.ToArray()); + { + //only write the json blob with a maximum count of the MaxProcessingInstructionCount + foreach (var maxBatch in instructions.InGroupsOf(Options.MaxProcessingInstructionCount)) + { + WriteInstructions(maxBatch); + } + } else + { batch.Add(new RefreshInstructionEnvelope(servers, refresher, instructions)); + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/TrySkipIisCustomErrorsCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/TrySkipIisCustomErrorsCheck.cs index 30eaa2666e..4ebf700ace 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Config/TrySkipIisCustomErrorsCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/TrySkipIisCustomErrorsCheck.cs @@ -27,7 +27,8 @@ namespace Umbraco.Web.HealthCheck.Checks.Config { get { - var recommendedValue = _serverVersion >= new Version("7.5.0") + // beware! 7.5 and 7.5.0 are not the same thing! + var recommendedValue = _serverVersion >= new Version("7.5") ? bool.TrueString.ToLower() : bool.FalseString.ToLower(); return new List { new AcceptableConfiguration { IsRecommended = true, Value = recommendedValue } }; @@ -39,7 +40,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Config get { return TextService.Localize("healthcheck/trySkipIisCustomErrorsCheckSuccessMessage", - new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, _serverVersion.ToString() }); + new[] { Values.First(v => v.IsRecommended).Value, _serverVersion.ToString() }); } } diff --git a/src/Umbraco.Web/HtmlStringUtilities.cs b/src/Umbraco.Web/HtmlStringUtilities.cs index 421a4c025b..30429173c1 100644 --- a/src/Umbraco.Web/HtmlStringUtilities.cs +++ b/src/Umbraco.Web/HtmlStringUtilities.cs @@ -215,6 +215,13 @@ namespace Umbraco.Web if (!lengthReached && currentTextLength >= length) { + // if the last character added was the first of a two character unicode pair, add the second character + if (Char.IsHighSurrogate((char)ic)) + { + var lowSurrogate = tr.Read(); + outputtw.Write((char)lowSurrogate); + } + // Reached truncate limit. if (addElipsis) { diff --git a/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs b/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs index 4cff44ba2f..668c9abecf 100644 --- a/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs @@ -68,7 +68,7 @@ namespace Umbraco.Web.Models.Mapping }); config.CreateMap() - .ForMember(display => display.AvailableEditors, expression => expression.ResolveUsing()) + .ForMember(display => display.AvailableEditors, expression => expression.ResolveUsing(new AvailablePropertyEditorsResolver())) .ForMember(display => display.PreValues, expression => expression.ResolveUsing(new PreValueDisplayResolver(lazyDataTypeService))) .ForMember(display => display.SelectedEditor, expression => expression.MapFrom( definition => definition.PropertyEditorAlias.IsNullOrWhiteSpace() ? null : definition.PropertyEditorAlias)) @@ -104,7 +104,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(definition => definition.Key, expression => expression.Ignore()) .ForMember(definition => definition.Path, expression => expression.Ignore()) .ForMember(definition => definition.PropertyEditorAlias, expression => expression.MapFrom(save => save.SelectedEditor)) - .ForMember(definition => definition.DatabaseType, expression => expression.ResolveUsing()) + .ForMember(definition => definition.DatabaseType, expression => expression.ResolveUsing(new DatabaseTypeResolver())) .ForMember(x => x.CreatorId, expression => expression.Ignore()) .ForMember(x => x.Level, expression => expression.Ignore()) .ForMember(x => x.SortOrder, expression => expression.Ignore()) diff --git a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs index 9bc47eb8a7..0c38879c7d 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs @@ -89,7 +89,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.Tabs, expression => expression.ResolveUsing(new MemberTabsAndPropertiesResolver(_textService))) .ForMember(display => display.MemberProviderFieldMapping, - expression => expression.ResolveUsing()) + expression => expression.ResolveUsing(new MemberProviderFieldMappingResolver())) .ForMember(display => display.MembershipScenario, expression => expression.ResolveUsing(new MembershipScenarioMappingResolver(new Lazy(() => _memberTypeService)))) .ForMember(display => display.Notifications, expression => expression.Ignore()) @@ -170,7 +170,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(x => x.Alias, expression => expression.Ignore()) .ForMember(member => member.HasPublishedVersion, expression => expression.Ignore()) //do no map the custom member properties (currently anyways, they were never there in 6.x) - .ForMember(dto => dto.Properties, expression => expression.ResolveUsing()); + .ForMember(dto => dto.Properties, expression => expression.ResolveUsing(new MemberDtoPropertiesValueResolver())); //FROM IMemberGroup TO MemberGroupDisplay config.CreateMap() diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs index d5f21d3e5f..ff2903fd3c 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs @@ -63,10 +63,9 @@ namespace Umbraco.Web.PropertyEditors return currentValue; // get the current file paths - var fs = _mediaFileSystem; var currentPaths = currentValue.ToString() .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => fs.GetRelativePath(x)) // get the fs-relative path + .Select(x => _mediaFileSystem.GetRelativePath(x)) // get the fs-relative path .ToArray(); // if clearing, remove these files and return @@ -110,9 +109,9 @@ namespace Umbraco.Web.PropertyEditors using (var filestream = File.OpenRead(file.TempFilePath)) { - fs.AddFile(filepath, filestream, true); // must overwrite! + _mediaFileSystem.AddFile(filepath, filestream, true); // must overwrite! - var ext = fs.GetExtension(filepath); + var ext = _mediaFileSystem.GetExtension(filepath); if (_mediaFileSystem.IsImageFile(ext)) { var preValues = editorValue.PreValues.FormatAsDictionary(); @@ -137,7 +136,7 @@ namespace Umbraco.Web.PropertyEditors _mediaFileSystem.DeleteFile(pathToRemove, true); - return string.Join(",", newPaths.Select(x => fs.GetUrl(x))); + return string.Join(",", newPaths.Select(x => _mediaFileSystem.GetUrl(x))); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/PublishedCache/NuCache/FacadeService.cs b/src/Umbraco.Web/PublishedCache/NuCache/FacadeService.cs index 8b19f5a865..ae9c3d4a33 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/FacadeService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/FacadeService.cs @@ -1242,7 +1242,7 @@ WHERE cmsContentNu.nodeId IN ( } items.AddRange(guids.Select(x => GetDto(repository.GetByVersion(x), true))); - db.BulkInsertRecords(db.SqlSyntax, items); + db.BulkInsertRecords(items); processed += items.Count; } while (processed < total); } @@ -1302,7 +1302,7 @@ WHERE cmsContentNu.nodeId IN ( { var descendants = repository.GetPagedResultsByQuery(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); var items = descendants.Select(m => GetDto(m, true)).ToArray(); - db.BulkInsertRecords(db.SqlSyntax, items); + db.BulkInsertRecords(items); processed += items.Length; } while (processed < total); } @@ -1362,7 +1362,7 @@ WHERE cmsContentNu.nodeId IN ( { var descendants = repository.GetPagedResultsByQuery(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); var items = descendants.Select(m => GetDto(m, true)).ToArray(); - db.BulkInsertRecords(db.SqlSyntax, items); + db.BulkInsertRecords(items); processed += items.Length; } while (processed < total); } diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs index 247942df51..ae2f3f8866 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs @@ -11,6 +11,8 @@ using Umbraco.Core.Xml; using Umbraco.Web.Routing; using System.Linq; using Umbraco.Core.Cache; +using Umbraco.Core.Services; +using Task = System.Threading.Tasks.Task; namespace Umbraco.Web.PublishedCache.XmlPublishedCache { @@ -132,6 +134,13 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache // - non-colliding, adds one complete "by route" lookup, only on the first time a url is computed (then it's cached anyways) // - colliding, adds one "by route" lookup, the first time the url is computed, then one dictionary looked each time it is computed again // assuming no collisions, the impact is one complete "by route" lookup the first time each url is computed + // + // U4-9121 - this lookup is too expensive when computing a large amount of urls on a front-end (eg menu) + // ... thinking about moving the lookup out of the path into its own async task, so we are not reporting errors + // in the back-office anymore, but at least we are not polluting the cache + // instead, refactored DeterminedIdByRoute to stop using XPath, with a 16x improvement according to benchmarks + // will it be enough? + var loopId = preview ? 0 : (_routesCache?.GetNodeId(route) ?? 0); // might be cached already in case of collision if (loopId == 0) { @@ -168,30 +177,98 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache var pos = route.IndexOf('/'); var path = pos == 0 ? route : route.Substring(pos); var startNodeId = pos == 0 ? 0 : int.Parse(route.Substring(0, pos)); - IEnumerable vars; - - var xpath = CreateXpathQuery(startNodeId, path, hideTopLevelNode, out vars); //check if we can find the node in our xml cache - var content = GetSingleByXPath(preview, xpath, vars?.ToArray()); + var id = NavigateRoute(preview, startNodeId, path, hideTopLevelNode); + if (id > 0) return GetById(preview, id); // if hideTopLevelNodePath is true then for url /foo we looked for /*/foo // but maybe that was the url of a non-default top-level node, so we also // have to look for /foo (see note in ApplyHideTopLevelNodeFromPath). - if (content == null && hideTopLevelNode && path.Length > 1 && path.IndexOf('/', 1) < 0) + if (hideTopLevelNode && path.Length > 1 && path.IndexOf('/', 1) < 0) { - xpath = CreateXpathQuery(startNodeId, path, false, out vars); - content = GetSingleByXPath(preview, xpath, vars?.ToArray()); + var id2 = NavigateRoute(preview, startNodeId, path, false); + if (id2 > 0) return GetById(preview, id2); } - return content; + return null; + } + + private int NavigateRoute(bool preview, int startNodeId, string path, bool hideTopLevelNode) + { + var xml = GetXml(preview); + XmlElement elt; + + // empty path + if (path == string.Empty || path == "/") + { + if (startNodeId > 0) + { + elt = xml.GetElementById(startNodeId.ToString(CultureInfo.InvariantCulture)); + return elt == null ? -1 : startNodeId; + } + + elt = null; + var min = int.MaxValue; + foreach (XmlElement e in xml.DocumentElement.ChildNodes) + { + var sortOrder = int.Parse(e.GetAttribute("sortOrder")); + if (sortOrder < min) + { + min = sortOrder; + elt = e; + } + } + return elt == null ? -1 : int.Parse(elt.GetAttribute("id")); + } + + // non-empty path + elt = startNodeId <= 0 + ? xml.DocumentElement + : xml.GetElementById(startNodeId.ToString(CultureInfo.InvariantCulture)); + if (elt == null) return -1; + + var urlParts = path.Split(SlashChar, StringSplitOptions.RemoveEmptyEntries); + + if (hideTopLevelNode && startNodeId <= 0) + { + foreach (XmlElement e in elt.ChildNodes) + { + var id = NavigateElementRoute(e, urlParts); + if (id > 0) return id; + } + return -1; + } + + return NavigateElementRoute(elt, urlParts); + } + + private int NavigateElementRoute(XmlElement elt, string[] urlParts) + { + var found = true; + var i = 0; + while (found && i < urlParts.Length) + { + found = false; + foreach (XmlElement child in elt.ChildNodes) + { + var noNode = child.GetAttributeNode("isDoc") == null; + if (noNode) continue; + if (child.GetAttribute("urlName") != urlParts[i]) continue; + + found = true; + elt = child; + break; + } + i++; + } + return found ? int.Parse(elt.GetAttribute("id")) : -1; } string DetermineRouteById(bool preview, int contentId) { var node = GetById(preview, contentId); - if (node == null) - return null; + if (node == null) return null; // walk up from that node until we hit a node with a domain, // or we reach the content root, collecting urls in the way @@ -253,10 +330,6 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { public const string Root = "/root"; public const string RootDocuments = "/root/* [@isDoc]"; - public const string DescendantDocumentById = "//* [@isDoc and @id={0}]"; - public const string ChildDocumentByUrlName = "/* [@isDoc and @urlName='{0}']"; - public const string ChildDocumentByUrlNameVar = "/* [@isDoc and @urlName=${0}]"; - public const string RootDocumentWithLowestSortOrder = "/root/* [@isDoc and not(@sortOrder > ../* [@isDoc]/@sortOrder)][1]"; } #endregion @@ -436,82 +509,6 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache static readonly char[] SlashChar = { '/' }; - protected string CreateXpathQuery(int startNodeId, string path, bool hideTopLevelNodeFromPath, out IEnumerable vars) - { - string xpath; - vars = null; - - if (path == string.Empty || path == "/") - { - // if url is empty - if (startNodeId > 0) - { - // if in a domain then use the root node of the domain - xpath = string.Format(XPathStrings.Root + XPathStrings.DescendantDocumentById, startNodeId); - } - else - { - // if not in a domain - what is the default page? - // let's say it is the first one in the tree, if any -- order by sortOrder - - // but! - // umbraco does not consistently guarantee that sortOrder starts with 0 - // so the one that we want is the one with the smallest sortOrder - // read http://stackoverflow.com/questions/1128745/how-can-i-use-xpath-to-find-the-minimum-value-of-an-attribute-in-a-set-of-elemen - - // so that one does not work, because min(@sortOrder) maybe 1 - // xpath = "/root/*[@isDoc and @sortOrder='0']"; - - // and we can't use min() because that's XPath 2.0 - // that one works - xpath = XPathStrings.RootDocumentWithLowestSortOrder; - } - } - else - { - // if url is not empty, then use it to try lookup a matching page - var urlParts = path.Split(SlashChar, StringSplitOptions.RemoveEmptyEntries); - var xpathBuilder = new StringBuilder(); - var partsIndex = 0; - List varsList = null; - - if (startNodeId == 0) - { - // if hiding, first node is not in the url - xpathBuilder.Append(hideTopLevelNodeFromPath ? XPathStrings.RootDocuments : XPathStrings.Root); - } - else - { - xpathBuilder.AppendFormat(XPathStrings.Root + XPathStrings.DescendantDocumentById, startNodeId); - // always "hide top level" when there's a domain - } - - while (partsIndex < urlParts.Length) - { - var part = urlParts[partsIndex++]; - if (part.Contains('\'') || part.Contains('"')) - { - // use vars, escaping gets ugly pretty quickly - varsList = varsList ?? new List(); - var varName = $"var{partsIndex}"; - varsList.Add(new XPathVariable(varName, part)); - xpathBuilder.AppendFormat(XPathStrings.ChildDocumentByUrlNameVar, varName); - } - else - { - xpathBuilder.AppendFormat(XPathStrings.ChildDocumentByUrlName, part); - - } - } - - xpath = xpathBuilder.ToString(); - if (varsList != null) - vars = varsList.ToArray(); - } - - return xpath; - } - #endregion #region Content types diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs index 8b446956e9..f6c879a228 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs @@ -305,7 +305,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache // that has been deleted, hence is not in the Examine index anymore (for a good reason). try to get // the media from the service, first var media = _mediaService.GetById(id); - if (media == null) return null; // not found, ok + if (media == null || media.Trashed) return null; // not found, ok // so, the media was not found in Examine's index *yet* it exists, which probably indicates that // the index is corrupted. Or not up-to-date. Log a warning, but only once, and only if seeing the diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs index 74cd28954b..fd6ebe552d 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs @@ -1825,7 +1825,7 @@ WHERE cmsContentXml.nodeId IN ( // because we already have the condition on the content being published var descendants = repository.GetPagedResultsByQuery(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true, newest: false); var items = descendants.Select(c => new ContentXmlDto { NodeId = c.Id, Xml = _xmlContentSerializer(c).ToDataString() }).ToArray(); - db.BulkInsertRecords(db.SqlSyntax, items); + db.BulkInsertRecords(items); processed += items.Length; } while (processed < total); } @@ -1901,7 +1901,7 @@ WHERE cmsPreviewXml.nodeId IN ( NodeId = c.Id, Xml = _xmlContentSerializer(c).ToDataString() }).ToArray(); - db.BulkInsertRecords(db.SqlSyntax, items); + db.BulkInsertRecords(items); processed += items.Length; } while (processed < total); } @@ -1971,7 +1971,7 @@ WHERE cmsContentXml.nodeId IN ( { var descendants = repository.GetPagedResultsByQuery(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); var items = descendants.Select(m => new ContentXmlDto { NodeId = m.Id, Xml = _xmlMediaSerializer(m).ToDataString() }).ToArray(); - db.BulkInsertRecords(db.SqlSyntax, items); + db.BulkInsertRecords(items); processed += items.Length; } while (processed < total); } @@ -2041,7 +2041,7 @@ WHERE cmsContentXml.nodeId IN ( { var descendants = repository.GetPagedResultsByQuery(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); var items = descendants.Select(m => new ContentXmlDto { NodeId = m.Id, Xml = _xmlMemberSerializer(m).ToDataString() }).ToArray(); - db.BulkInsertRecords(db.SqlSyntax, items); + db.BulkInsertRecords(items); processed += items.Length; } while (processed < total); } diff --git a/src/Umbraco.Web/Search/LuceneIndexerExtensions.cs b/src/Umbraco.Web/Search/LuceneIndexerExtensions.cs index 66704d0c8b..e5e5f94a2f 100644 --- a/src/Umbraco.Web/Search/LuceneIndexerExtensions.cs +++ b/src/Umbraco.Web/Search/LuceneIndexerExtensions.cs @@ -5,6 +5,8 @@ using Examine.LuceneEngine.Providers; using Examine.Providers; using Lucene.Net.Index; using Lucene.Net.Search; +using Lucene.Net.Store; +using Umbraco.Core.Logging; namespace Umbraco.Web.Search { @@ -21,12 +23,20 @@ namespace Umbraco.Web.Search /// public static int GetIndexDocumentCount(this LuceneIndexer indexer) { - var searcher = indexer.GetSearcher().GetSearcher() as IndexSearcher; - if (searcher == null) return 0; - using (searcher) - using (var reader = searcher.IndexReader) + try { - return reader.NumDocs(); + var searcher = indexer.GetSearcher().GetSearcher() as IndexSearcher; + if (searcher == null) return 0; + using (searcher) + using (var reader = searcher.IndexReader) + { + return reader.NumDocs(); + } + } + catch (AlreadyClosedException) + { + Current.Logger.Warn(typeof(ExamineExtensions), "Cannot get GetIndexDocumentCount, the writer is already closed"); + return 0; } } @@ -37,12 +47,22 @@ namespace Umbraco.Web.Search /// public static int GetIndexFieldCount(this LuceneIndexer indexer) { - var searcher = indexer.GetSearcher().GetSearcher() as IndexSearcher; - if (searcher == null) return 0; - using (searcher) - using (var reader = searcher.IndexReader) + //TODO: check for closing! and AlreadyClosedException + + try { - return reader.GetFieldNames(IndexReader.FieldOption.ALL).Count; + var searcher = indexer.GetSearcher().GetSearcher() as IndexSearcher; + if (searcher == null) return 0; + using (searcher) + using (var reader = searcher.IndexReader) + { + return reader.GetFieldNames(IndexReader.FieldOption.ALL).Count; + } + } + catch (AlreadyClosedException) + { + Current.Logger.Warn(typeof(ExamineExtensions), "Cannot get GetIndexFieldCount, the writer is already closed"); + return 0; } } @@ -53,12 +73,20 @@ namespace Umbraco.Web.Search /// public static bool IsIndexOptimized(this LuceneIndexer indexer) { - var searcher = indexer.GetSearcher().GetSearcher() as IndexSearcher; - if (searcher == null) return true; - using (searcher) - using (var reader = searcher.IndexReader) + try { - return reader.IsOptimized(); + var searcher = indexer.GetSearcher().GetSearcher() as IndexSearcher; + if (searcher == null) return true; + using (searcher) + using (var reader = searcher.IndexReader) + { + return reader.IsOptimized(); + } + } + catch (AlreadyClosedException) + { + Current.Logger.Warn(typeof(ExamineExtensions), "Cannot get IsIndexOptimized, the writer is already closed"); + return false; } } @@ -83,12 +111,20 @@ namespace Umbraco.Web.Search /// public static int GetDeletedDocumentsCount(this LuceneIndexer indexer) { - var searcher = indexer.GetSearcher().GetSearcher() as IndexSearcher; - if (searcher == null) return 0; - using (searcher) - using (var reader = searcher.IndexReader) + try { - return reader.NumDeletedDocs; + var searcher = indexer.GetSearcher().GetSearcher() as IndexSearcher; + if (searcher == null) return 0; + using (searcher) + using (var reader = searcher.IndexReader) + { + return reader.NumDeletedDocs; + } + } + catch (AlreadyClosedException) + { + Current.Logger.Warn(typeof(ExamineExtensions), "Cannot get GetDeletedDocumentsCount, the writer is already closed"); + return 0; } } } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/EditDictionaryItem.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/EditDictionaryItem.aspx.cs index affa0451aa..e7cb51a650 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/EditDictionaryItem.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/EditDictionaryItem.aspx.cs @@ -5,48 +5,72 @@ using System.Web.UI.WebControls; using umbraco.cms.presentation.Trees; using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; using Umbraco.Web; using Umbraco.Web.UI; namespace umbraco.settings { - /// - /// Summary description for EditDictionaryItem. - /// + /// + /// Summary description for EditDictionaryItem. + /// [WebformsPageTreeAuthorize(Constants.Trees.Dictionary)] public partial class EditDictionaryItem : Umbraco.Web.UI.Pages.UmbracoEnsuredPage - { - - protected LiteralControl keyTxt = new LiteralControl(); - protected uicontrols.TabView tbv = new uicontrols.TabView(); - private System.Collections.ArrayList languageFields = new System.Collections.ArrayList(); + { + protected LiteralControl keyTxt = new LiteralControl(); + protected uicontrols.TabView tbv = new uicontrols.TabView(); + private System.Collections.ArrayList languageFields = new System.Collections.ArrayList(); private IDictionaryItem currentItem; + protected TextBox boxChangeKey; + protected Label labelChangeKey; + protected Literal txt; - protected void Page_Load(object sender, System.EventArgs e) - { - currentItem = Services.LocalizationService.GetDictionaryItemById(int.Parse(Request.QueryString["id"])); + protected void Page_Load(object sender, System.EventArgs e) + { + currentItem = Services.LocalizationService.GetDictionaryItemById(int.Parse(Request.QueryString["id"])); - // Put user code to initialize the page here - Panel1.hasMenu = true; + // Put user code to initialize the page here + Panel1.hasMenu = true; Panel1.Text = Services.TextService.Localize("editdictionary") + ": " + currentItem.ItemKey; - - uicontrols.Pane p = new uicontrols.Pane(); - var save = Panel1.Menu.NewButton(); + var save = Panel1.Menu.NewButton(); save.Text = Services.TextService.Localize("save"); save.Click += save_Click; save.ToolTip = Services.TextService.Localize("save"); save.ID = "save"; save.ButtonType = uicontrols.MenuButtonType.Primary; - Literal txt = new Literal(); + uicontrols.Pane p = new uicontrols.Pane(); + + boxChangeKey = new TextBox + { + ID = "changeKey-" + currentItem.Id, + CssClass = "umbEditorTextField", + Text = currentItem.ItemKey + }; + + labelChangeKey = new Label + { + ID = "changeKeyLabel", + CssClass = "text-error" + }; + + p.addProperty(new Literal + { + Text = "

" + Services.TextService.Localize("dictionaryItem/changeKey") + "

" + }); + p.addProperty(boxChangeKey); + p.addProperty(labelChangeKey); + + + txt = new Literal(); txt.Text = "

" + Services.TextService.Localize("dictionaryItem/description", new[] { currentItem.ItemKey }) + "


"; p.addProperty(txt); - - foreach (cms.businesslogic.language.Language l in cms.businesslogic.language.Language.getAll) - { - + + foreach (cms.businesslogic.language.Language l in cms.businesslogic.language.Language.getAll) + { + TextBox languageBox = new TextBox(); languageBox.TextMode = TextBoxMode.MultiLine; languageBox.ID = l.id.ToString(); @@ -60,68 +84,92 @@ namespace umbraco.settings languageFields.Add(languageBox); p.addProperty(l.FriendlyName, languageBox); - } + } - if (!IsPostBack) - { - var path = BuildPath(currentItem); - ClientTools + + if (!IsPostBack) + { + var path = BuildPath(currentItem); + ClientTools .SetActiveTreeType(Constants.Trees.Dictionary) .SyncTree(path, false); - } - + } Panel1.Controls.Add(p); - } + } private string BuildPath(IDictionaryItem current) - { + { var parentPath = current.ParentId.HasValue == false ? "" : BuildPath(current) + ","; return parentPath + current.Id; - } + } void save_Click(object sender, EventArgs e) { - foreach (TextBox t in languageFields) + labelChangeKey.Text = ""; // reset error text + var newKey = boxChangeKey.Text; + var save = true; + if (string.IsNullOrWhiteSpace(newKey) == false && newKey != currentItem.ItemKey) { - //check for null but allow empty string! - // http://issues.umbraco.org/issue/U4-1931 - if (t.Text != null) - { - Services.LocalizationService.AddOrUpdateDictionaryValue( - currentItem, - Services.LocalizationService.GetLanguageById(int.Parse(t.ID)), - t.Text); - - Services.LocalizationService.Save(currentItem); - + if (Services.LocalizationService.DictionaryItemExists(newKey)) + { + // reject + labelChangeKey.Text = Services.TextService.Localize("dictionaryItem/changeKeyError", newKey); + boxChangeKey.Text = currentItem.ItemKey; // reset key + save = false; } - } - ClientTools.ShowSpeechBubble(SpeechBubbleIcon.Save, Services.TextService.Localize("speechBubbles/dictionaryItemSaved"), ""); - } - #region Web Form Designer generated code - override protected void OnInit(EventArgs e) - { - // - // CODEGEN: This call is required by the ASP.NET Web Form Designer. - // - /* - tbv.ID="tabview1"; - tbv.Width = 400; - tbv.Height = 200; - */ + else + { + // update key + currentItem.ItemKey = newKey; - InitializeComponent(); - base.OnInit(e); - } - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - } - #endregion + // update title + Panel1.title.InnerHtml = Services.TextService.Localize("editdictionary") + ": " + newKey; + + // sync the content tree + var path = BuildPath(currentItem); + ClientTools.SyncTree(path, true); + } + } + + if (save) + { + foreach (TextBox t in languageFields) + { + //check for null but allow empty string! + // http://issues.umbraco.org/issue/U4-1931 + if (t.Text != null) + { + Services.LocalizationService.AddOrUpdateDictionaryValue( + currentItem, + Services.LocalizationService.GetLanguageById(int.Parse(t.ID)), + t.Text); + } + } + + Services.LocalizationService.Save(currentItem); + ClientTools.ShowSpeechBubble(SpeechBubbleIcon.Save, Services.TextService.Localize("speechBubbles/dictionaryItemSaved"), ""); + } + + txt.Text = "

" + Services.TextService.Localize("dictionaryItem/description", currentItem.ItemKey) + "


"; + } + + #region Web Form Designer generated code + + override protected void OnInit(EventArgs e) + { + InitializeComponent(); + base.OnInit(e); + } + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + } + + #endregion } } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs index 39bfd9c3f5..1e3292a4aa 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs @@ -22,6 +22,9 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Web.UI; using Umbraco.Web.UI.Pages; +using System.Text.RegularExpressions; +using System.Text; +using Umbraco.Core.Security; namespace umbraco.cms.presentation.user { @@ -35,11 +38,16 @@ namespace umbraco.cms.presentation.user CurrentApp = Constants.Applications.Users.ToString(); } protected HtmlTable macroProperties; - protected TextBox uname = new TextBox(); - protected TextBox lname = new TextBox(); + protected TextBox uname = new TextBox() { ID = "uname" }; + protected RequiredFieldValidator unameValidator = new RequiredFieldValidator(); + protected TextBox lname = new TextBox() { ID = "lname" }; + protected RequiredFieldValidator lnameValidator = new RequiredFieldValidator(); + protected CustomValidator lnameCustomValidator = new CustomValidator(); protected PlaceHolder passw = new PlaceHolder(); protected CheckBoxList lapps = new CheckBoxList(); - protected TextBox email = new TextBox(); + protected TextBox email = new TextBox() { ID = "email" }; + protected RequiredFieldValidator emailValidator = new RequiredFieldValidator(); + protected CustomValidator emailCustomValidator = new CustomValidator(); protected DropDownList userType = new DropDownList(); protected DropDownList userLanguage = new DropDownList(); protected CheckBox NoConsole = new CheckBox(); @@ -140,8 +148,7 @@ namespace umbraco.cms.presentation.user contentPicker.Value = "-1"; content.Controls.Add(contentPicker); - - + // Add password changer var passwordChanger = (passwordChanger)LoadControl(SystemDirectories.Umbraco + "/controls/passwordChanger.ascx"); passwordChanger.MembershipProviderName = UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider; @@ -165,10 +172,20 @@ namespace umbraco.cms.presentation.user passw.Controls.Add(passwordChanger); passw.Controls.Add(validatorContainer); - pp.addProperty(Services.TextService.Localize("user/username"), uname); - pp.addProperty(Services.TextService.Localize("user/loginname"), lname); + var validationSummary = new ValidationSummary + { + ID = "validationSummary", + DisplayMode = ValidationSummaryDisplayMode.BulletList, + CssClass = "error" + }; + + pp.addProperty(validationSummary); + + pp.addProperty(Services.TextService.Localize("user/username"), uname, unameValidator); + pp.addProperty(Services.TextService.Localize("user/loginname"), lname, lnameValidator, lnameCustomValidator); pp.addProperty(Services.TextService.Localize("user/password"), passw); - pp.addProperty(Services.TextService.Localize("email"), email); + + pp.addProperty(Services.TextService.Localize("general/email"), email, emailValidator, emailCustomValidator); pp.addProperty(Services.TextService.Localize("user/usertype"), userType); pp.addProperty(Services.TextService.Localize("user/language"), userLanguage); @@ -205,12 +222,56 @@ namespace umbraco.cms.presentation.user save.Text = Services.TextService.Localize("save"); save.ButtonType = MenuButtonType.Primary; - sectionValidator.ServerValidate += new ServerValidateEventHandler(sectionValidator_ServerValidate); + sectionValidator.ServerValidate += SectionValidator_OnServerValidate; sectionValidator.ControlToValidate = lapps.ID; sectionValidator.ErrorMessage = Services.TextService.Localize("errorHandling/errorMandatoryWithoutTab", new[] { Services.TextService.Localize("user/modules") }); sectionValidator.CssClass = "error"; sectionValidator.Style.Add("color", "red"); + unameValidator.ControlToValidate = uname.ID; + unameValidator.Display = ValidatorDisplay.Dynamic; + unameValidator.ErrorMessage = Services.TextService.Localize("defaultdialogs/requiredField"); + unameValidator.CssClass = "error"; + unameValidator.Style.Add("color", "red"); + unameValidator.Style.Add("margin-left", "5px"); + unameValidator.Style.Add("line-height", "28px"); + + lnameValidator.ControlToValidate = lname.ID; + lnameValidator.Display = ValidatorDisplay.Dynamic; + lnameValidator.ErrorMessage = Services.TextService.Localize("defaultdialogs/requiredField"); + lnameValidator.CssClass = "error"; + lnameValidator.Style.Add("color", "red"); + lnameValidator.Style.Add("margin-left", "5px"); + lnameValidator.Style.Add("line-height", "28px"); + + lnameCustomValidator.ServerValidate += LnameCustomValidator_OnServerValidate; + lnameCustomValidator.Display = ValidatorDisplay.Dynamic; + lnameCustomValidator.ControlToValidate = lname.ID; + var localizedLname = Services.TextService.Localize("user/loginname"); + lnameCustomValidator.ErrorMessage = Services.TextService.Localize("errorHandling/errorExistsWithoutTab", localizedLname); + lnameCustomValidator.CssClass = "error"; + lnameCustomValidator.Style.Add("color", "red"); + lnameCustomValidator.Style.Add("margin-left", "5px"); + lnameCustomValidator.Style.Add("line-height", "28px"); + + emailValidator.ControlToValidate = email.ID; + emailValidator.Display = ValidatorDisplay.Dynamic; + emailValidator.ErrorMessage = Services.TextService.Localize("defaultdialogs/requiredField"); + emailValidator.CssClass = "error"; + emailValidator.Style.Add("color", "red"); + emailValidator.Style.Add("margin-left", "5px"); + emailValidator.Style.Add("line-height", "28px"); + + emailCustomValidator.ServerValidate += EmailCustomValidator_OnServerValidate; + emailCustomValidator.Display = ValidatorDisplay.Dynamic; + emailCustomValidator.ControlToValidate = email.ID; + var localizedEmail = Services.TextService.Localize("general/email"); + emailCustomValidator.ErrorMessage = Services.TextService.Localize("errorHandling/errorRegExpWithoutTab", localizedEmail); + emailCustomValidator.CssClass = "error"; + emailCustomValidator.Style.Add("color", "red"); + emailCustomValidator.Style.Add("margin-left", "5px"); + emailCustomValidator.Style.Add("line-height", "28px"); + SetupForm(); ClientTools @@ -218,8 +279,18 @@ namespace umbraco.cms.presentation.user .SyncTree(UID.ToString(), false); } + private void LnameCustomValidator_OnServerValidate(object source, ServerValidateEventArgs args) + { + var usersWithLoginName = Services.UserService.GetByUsername(lname.Text); + args.IsValid = usersWithLoginName == null || usersWithLoginName.Id == u.Id; + } - void sectionValidator_ServerValidate(object source, ServerValidateEventArgs args) + private void EmailCustomValidator_OnServerValidate(object source, ServerValidateEventArgs args) + { + args.IsValid = MembershipProviderBase.IsEmailValid(email.Text.Trim()); + } + + private void SectionValidator_OnServerValidate(object source, ServerValidateEventArgs args) { args.IsValid = false || lapps.SelectedIndex >= 0; } @@ -392,7 +463,9 @@ namespace umbraco.cms.presentation.user } else { - ClientTools.ShowSpeechBubble(SpeechBubbleIcon.Error, Services.TextService.Localize("speechBubbles/editUserError"), ""); + ClientTools.ShowSpeechBubble(SpeechBubbleIcon.Error, + Services.TextService.Localize("speechBubbles/validationFailedHeader"), + Services.TextService.Localize("speechBubbles/validationFailedMessage")); } } diff --git a/src/umbraco.controls/pane.cs b/src/umbraco.controls/pane.cs index 0facc946a3..499695debf 100644 --- a/src/umbraco.controls/pane.cs +++ b/src/umbraco.controls/pane.cs @@ -36,6 +36,22 @@ namespace umbraco.uicontrols set { m_title = value; } } + public void addProperty(string Caption, Control C, params BaseValidator[] validators) + { + + PropertyPanel pp = new PropertyPanel(); + pp.Controls.Add(C); + + foreach (var validator in validators) + { + validator.Display = ValidatorDisplay.Dynamic; + pp.Controls.Add(validator); + } + pp.Text = Caption; + + this.Controls.Add(pp); + } + public void addProperty(string Caption, Control C) { diff --git a/src/umbraco.sln b/src/umbraco.sln index 77450f39a1..492a845c87 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -105,6 +105,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{5B03EF4E ..\build\NuSpecs\build\UmbracoCms.targets = ..\build\NuSpecs\build\UmbracoCms.targets EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Tests.Benchmarks", "Umbraco.Tests.Benchmarks\Umbraco.Tests.Benchmarks.csproj", "{86DEB346-089F-4106-89C8-D852B9CF2A33}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Compat7", "Umbraco.Compat7\Umbraco.Compat7.csproj", "{185E098F-5706-4B97-B404-EB974F05F633}" EndProject Global @@ -151,6 +153,10 @@ Global {07FBC26B-2927-4A22-8D96-D644C667FECC}.Debug|Any CPU.Build.0 = Debug|Any CPU {07FBC26B-2927-4A22-8D96-D644C667FECC}.Release|Any CPU.ActiveCfg = Release|Any CPU {07FBC26B-2927-4A22-8D96-D644C667FECC}.Release|Any CPU.Build.0 = Release|Any CPU + {86DEB346-089F-4106-89C8-D852B9CF2A33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {86DEB346-089F-4106-89C8-D852B9CF2A33}.Debug|Any CPU.Build.0 = Debug|Any CPU + {86DEB346-089F-4106-89C8-D852B9CF2A33}.Release|Any CPU.ActiveCfg = Release|Any CPU + {86DEB346-089F-4106-89C8-D852B9CF2A33}.Release|Any CPU.Build.0 = Release|Any CPU {185E098F-5706-4B97-B404-EB974F05F633}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {185E098F-5706-4B97-B404-EB974F05F633}.Debug|Any CPU.Build.0 = Debug|Any CPU {185E098F-5706-4B97-B404-EB974F05F633}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -164,5 +170,6 @@ Global {5D3B8245-ADA6-453F-A008-50ED04BFE770} = {B5BD12C1-A454-435E-8A46-FF4A364C0382} {E3F9F378-AFE1-40A5-90BD-82833375DBFE} = {227C3B55-80E5-4E7E-A802-BE16C5128B9D} {5B03EF4E-E0AC-4905-861B-8C3EC1A0D458} = {227C3B55-80E5-4E7E-A802-BE16C5128B9D} + {86DEB346-089F-4106-89C8-D852B9CF2A33} = {B5BD12C1-A454-435E-8A46-FF4A364C0382} EndGlobalSection EndGlobal diff --git a/src/umbraco.sln.DotSettings b/src/umbraco.sln.DotSettings index dd168f105b..b887bc170b 100644 --- a/src/umbraco.sln.DotSettings +++ b/src/umbraco.sln.DotSettings @@ -16,5 +16,5 @@ Disposable construction HINT False - CSharp50 + CSharp60 \ No newline at end of file diff --git a/vimeo.png b/vimeo.png new file mode 100644 index 0000000000..d3c12465ba Binary files /dev/null and b/vimeo.png differ