More SqlTemplates

This commit is contained in:
Stephan
2017-09-24 18:54:31 +02:00
parent 5ba2ffcbf3
commit e71c9740cd
14 changed files with 364 additions and 37 deletions

View File

@@ -14,7 +14,7 @@
string AuthCookieName { get; }
string AuthCookieDomain { get; }
/// <summary>
/// A boolean indicating that by default the email address will be the username
/// </summary>

View File

@@ -18,7 +18,7 @@
/// The alias of the external content indexer
/// </summary>
public const string ExternalIndexer = "ExternalIndexer";
/// <summary>
/// The alias of the internal member searcher
/// </summary>

View File

@@ -42,7 +42,7 @@ namespace Umbraco.Core
Minute,
Second
}
/// <summary>
/// Calculates the number of minutes from a date time, on a rolling daily basis (so if
/// date time is before the time, calculate onto next day)
@@ -57,10 +57,10 @@ namespace Umbraco.Core
{
scheduledTime = "0" + scheduledTime;
}
var scheduledHour = int.Parse(scheduledTime.Substring(0, 2));
var scheduledMinute = int.Parse(scheduledTime.Substring(2));
DateTime scheduledDateTime;
if (IsScheduledInRemainingDay(fromDateTime, scheduledHour, scheduledMinute))
{
@@ -71,10 +71,10 @@ namespace Umbraco.Core
var nextDay = fromDateTime.AddDays(1);
scheduledDateTime = new DateTime(nextDay.Year, nextDay.Month, nextDay.Day, scheduledHour, scheduledMinute, 0);
}
return (int)(scheduledDateTime - fromDateTime).TotalMinutes;
}
private static bool IsScheduledInRemainingDay(DateTime fromDateTime, int scheduledHour, int scheduledMinute)
{
return scheduledHour > fromDateTime.Hour || (scheduledHour == fromDateTime.Hour && scheduledMinute >= fromDateTime.Minute);

View File

@@ -12,7 +12,7 @@ namespace Umbraco.Core.Persistence.Migrations
ILogger Logger { get; }
ILocalMigration GetLocalMigration();
ISqlContext SqlContext { get; }
}
}

View File

@@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence.Querying
/// </summary>
public string VisitResult
{
get { return _visitResult; }
get => _visitResult;
set
{
if (Visited)

View File

@@ -20,6 +20,41 @@ namespace Umbraco.Core.Persistence.Querying
_pd = sqlContext.PocoDataFactory.ForType(typeof(TDto));
}
protected override string VisitMethodCall(MethodCallExpression m)
{
var declaring = m.Method.DeclaringType;
if (declaring != typeof (SqlTemplate))
return base.VisitMethodCall(m);
if (m.Method.Name != "Arg" && m.Method.Name != "ArgIn")
throw new NotSupportedException($"Method SqlTemplate.{m.Method.Name} is not supported.");
var parameters = m.Method.GetParameters();
if (parameters.Length != 1 || parameters[0].ParameterType != typeof (string))
throw new NotSupportedException($"Method SqlTemplate.{m.Method.Name}({string.Join(", ", parameters.Select(x => x.ParameterType))} is not supported.");
var arg = m.Arguments[0];
string name;
if (arg.NodeType == ExpressionType.Constant)
{
name = arg.ToString();
}
else
{
// though... we probably should avoid doing this
var member = Expression.Convert(arg, typeof (object));
var lambda = Expression.Lambda<Func<object>>(member);
var getter = lambda.Compile();
name = getter().ToString();
}
SqlParameters.Add(RemoveQuote(name));
return Visited
? string.Empty
: $"@{SqlParameters.Count - 1}";
}
protected override string VisitMemberAccess(MemberExpression m)
{
if (m.Expression != null && m.Expression.NodeType == ExpressionType.Parameter && m.Expression.Type == typeof(TDto))
@@ -95,6 +130,10 @@ namespace Umbraco.Core.Persistence.Querying
_parameterName1 = lambda.Parameters[0].Name;
_parameterName2 = lambda.Parameters[1].Name;
}
else
{
_parameterName1 = _parameterName2 = null;
}
return base.VisitLambda(lambda);
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NPoco;
@@ -29,22 +30,61 @@ namespace Umbraco.Core.Persistence
// must pass the args in the proper order, faster
public Sql<ISqlContext> Sql(params object[] args)
{
return new Sql<ISqlContext>(_sqlContext, _sql, args);
// if the type is an "unspeakable name" it is an anonymous compiler-generated object
// see https://stackoverflow.com/questions/9256594
// => assume it's an anonymous type object containing named arguments
// (of course this means we cannot use *real* objects here and need SqlNamed - bah)
if (args.Length == 1 && args[0].GetType().Name.Contains("<"))
return SqlNamed(args[0]);
if (args.Length != _args.Count)
throw new ArgumentException("Invalid number of arguments.", nameof(args));
if (args.Length == 0)
return new Sql<ISqlContext>(_sqlContext, true, _sql);
var isBuilt = !args.Any(x => x is IEnumerable);
return new Sql<ISqlContext>(_sqlContext, isBuilt, _sql, args);
}
// can pass named args, slower
// so, not much different from what Where(...) does (ie reflection)
public Sql<ISqlContext> SqlNamed(object nargs)
{
var isBuilt = true;
var args = new object[_args.Count];
var properties = nargs.GetType().GetProperties().ToDictionary(x => x.Name, x => x.GetValue(nargs));
for (var i = 0; i < _args.Count; i++)
{
if (!properties.TryGetValue(_args[i], out var value))
throw new InvalidOperationException($"Invalid argument name \"{_args[i]}\".");
throw new InvalidOperationException($"Missing argument \"{_args[i]}\".");
args[i] = value;
properties.Remove(_args[i]);
// if value is enumerable then we'll need to expand arguments
if (value is IEnumerable)
isBuilt = false;
}
return new Sql<ISqlContext>(_sqlContext, _sql, args);
if (properties.Count > 0)
throw new InvalidOperationException($"Unknown argument{(properties.Count > 1 ? "s" : "")}: {string.Join(", ", properties.Keys)}");
return new Sql<ISqlContext>(_sqlContext, isBuilt, _sql, args);
}
internal void WriteToConsole()
{
new Sql<ISqlContext>(_sqlContext, _sql, _args.Values.Cast<object>().ToArray()).WriteToConsole();
}
public static T Arg<T>(string name)
{
return default (T);
}
public static IEnumerable<T> ArgIn<T>(string name)
{
// don't return an empty enumerable, as it breaks NPoco
// fixme - should we cache these arrays?
return new[] { default (T) };
}
}
}

View File

@@ -15,6 +15,7 @@ namespace Umbraco.Tests.Benchmarks
//typeof(DeepCloneBenchmarks),
typeof(XmlPublishedContentInitBenchmarks),
typeof(CtorInvokeBenchmarks),
typeof(SqlTemplatesBenchmark),
});
switcher.Run(args);

View File

@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using NPoco;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Tests.Persistence.NPocoTests;
namespace Umbraco.Tests.Benchmarks
{
// seeing this kind of results - templates are gooood
//
// Method | Mean | Error | StdDev | Scaled | Gen 0 | Allocated |
// ---------------- |-----------:|----------:|----------:|-------:|---------:|----------:|
// WithoutTemplate | 2,895.0 us | 64.873 us | 99.068 us | 1.00 | 183.5938 | 762.23 KB |
// WithTemplate | 263.2 us | 4.581 us | 4.285 us | 0.09 | 50.2930 | 207.13 KB |
//
// though the difference might not be so obvious in case of WhereIn which requires parsing
[Config(typeof(Config))]
public class SqlTemplatesBenchmark
{
private class Config : ManualConfig
{
public Config()
{
Add(new MemoryDiagnoser());
}
}
public SqlTemplatesBenchmark()
{
var mappers = new NPoco.MapperCollection { new PocoMapper() };
var factory = new FluentPocoDataFactory((type, iPocoDataFactory) => new PocoDataBuilder(type, mappers).Init());
SqlContext = new SqlContext(new SqlCeSyntaxProvider(), DatabaseType.SQLCe, factory);
SqlTemplates = new SqlTemplates(SqlContext);
}
private ISqlContext SqlContext { get; }
private SqlTemplates SqlTemplates { get; }
[Benchmark(Baseline = true)]
public void WithoutTemplate()
{
for (var i = 0; i < 100; i++)
{
var sql = Sql.BuilderFor(SqlContext)
.Select<NPocoFetchTests.Thing1Dto>()
.From<NPocoFetchTests.Thing1Dto>()
.Where<NPocoFetchTests.Thing1Dto>(x => x.Name == "yada");
var sqlString = sql.SQL; // force-build the SQL
}
}
[Benchmark]
public void WithTemplate()
{
SqlTemplates.Clear();
for (var i = 0; i < 100; i++)
{
var template = SqlTemplates.Get("test", s => s
.Select<NPocoFetchTests.Thing1Dto>()
.From<NPocoFetchTests.Thing1Dto>()
.Where<NPocoFetchTests.Thing1Dto>(x => x.Name == SqlTemplate.Arg<string>("name")));
var sql = template.Sql(new { name = "yada" });
var sqlString = sql.SQL; // force-build the SQL
}
}
}
}

View File

@@ -170,6 +170,7 @@
<Compile Include="BulkInsertBenchmarks.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SqlTemplatesBenchmark.cs" />
<Compile Include="XmlBenchmarks.cs" />
<Compile Include="XmlPublishedContentInitBenchmarks.cs" />
</ItemGroup>

View File

@@ -3,7 +3,9 @@ using Moq;
using NPoco;
using NUnit.Framework;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core;
namespace Umbraco.Tests.Persistence.NPocoTests
{
@@ -11,7 +13,7 @@ namespace Umbraco.Tests.Persistence.NPocoTests
public class NPocoSqlTemplateTests
{
[Test]
public void TestSqlTemplates()
public void SqlTemplates()
{
var sqlContext = new SqlContext(new SqlCeSyntaxProvider(), DatabaseType.SQLCe, Mock.Of<IPocoDataFactory>());
var sqlTemplates = new SqlTemplates(sqlContext);
@@ -23,7 +25,7 @@ namespace Umbraco.Tests.Persistence.NPocoTests
var sql = sqlTemplates.Get("xxx", s => s
.SelectAll()
.From("zbThing1")
.Where("id=@id", new { id = "id" })).SqlNamed(new { id = 1 });
.Where("id=@id", new { id = "id" })).Sql(new { id = 1 });
sql.WriteToConsole();
@@ -31,9 +33,171 @@ namespace Umbraco.Tests.Persistence.NPocoTests
sql2.WriteToConsole();
var sql3 = sqlTemplates.Get("xxx", x => throw new InvalidOperationException("Should be cached.")).SqlNamed(new { id = 1 });
var sql3 = sqlTemplates.Get("xxx", x => throw new InvalidOperationException("Should be cached.")).Sql(new { id = 1 });
sql3.WriteToConsole();
}
[Test]
public void SqlTemplateArgs()
{
var mappers = new NPoco.MapperCollection { new PocoMapper() };
var factory = new FluentPocoDataFactory((type, iPocoDataFactory) => new PocoDataBuilder(type, mappers).Init());
var sqlContext = new SqlContext(new SqlCeSyntaxProvider(), DatabaseType.SQLCe, factory);
var sqlTemplates = new SqlTemplates(sqlContext);
const string sqlBase = "SELECT [zbThing1].[id] AS [Id], [zbThing1].[name] AS [Name] FROM [zbThing1] WHERE ";
var template = sqlTemplates.Get("sql1", s => s.Select<NPocoFetchTests.Thing1Dto>().From<NPocoFetchTests.Thing1Dto>()
.Where<NPocoFetchTests.Thing1Dto>(x => x.Name == "value"));
var sql = template.Sql("foo");
Assert.AreEqual(sqlBase + "(([zbThing1].[name] = @0))", sql.SQL.NoCrLf());
Assert.AreEqual(1, sql.Arguments.Length);
Assert.AreEqual("foo", sql.Arguments[0]);
sql = template.Sql(123);
Assert.AreEqual(sqlBase + "(([zbThing1].[name] = @0))", sql.SQL.NoCrLf());
Assert.AreEqual(1, sql.Arguments.Length);
Assert.AreEqual(123, sql.Arguments[0]);
template = sqlTemplates.Get("sql2", s => s.Select<NPocoFetchTests.Thing1Dto>().From<NPocoFetchTests.Thing1Dto>()
.Where<NPocoFetchTests.Thing1Dto>(x => x.Name == "value"));
sql = template.Sql(new { value = "foo" });
Assert.AreEqual(sqlBase + "(([zbThing1].[name] = @0))", sql.SQL.NoCrLf());
Assert.AreEqual(1, sql.Arguments.Length);
Assert.AreEqual("foo", sql.Arguments[0]);
sql = template.Sql(new { value = 123 });
Assert.AreEqual(sqlBase + "(([zbThing1].[name] = @0))", sql.SQL.NoCrLf());
Assert.AreEqual(1, sql.Arguments.Length);
Assert.AreEqual(123, sql.Arguments[0]);
Assert.Throws<InvalidOperationException>(() => template.Sql(new { xvalue = 123 }).WriteToConsole());
Assert.Throws<InvalidOperationException>(() => template.Sql(new { value = 123, xvalue = 456 }).WriteToConsole());
var i = 666;
template = sqlTemplates.Get("sql3", s => s.Select<NPocoFetchTests.Thing1Dto>().From<NPocoFetchTests.Thing1Dto>()
.Where<NPocoFetchTests.Thing1Dto>(x => x.Id == i));
sql = template.Sql("foo");
Assert.AreEqual(sqlBase + "(([zbThing1].[id] = @0))", sql.SQL.NoCrLf());
Assert.AreEqual(1, sql.Arguments.Length);
Assert.AreEqual("foo", sql.Arguments[0]);
sql = template.Sql(123);
Assert.AreEqual(sqlBase + "(([zbThing1].[id] = @0))", sql.SQL.NoCrLf());
Assert.AreEqual(1, sql.Arguments.Length);
Assert.AreEqual(123, sql.Arguments[0]);
// but we cannot name them, because the arg name is the value of "i"
// so we have to explicitely create the argument
template = sqlTemplates.Get("sql4", s => s.Select<NPocoFetchTests.Thing1Dto>().From<NPocoFetchTests.Thing1Dto>()
.Where<NPocoFetchTests.Thing1Dto>(x => x.Id == SqlTemplate.Arg<int>("i")));
sql = template.Sql("foo");
Assert.AreEqual(sqlBase + "(([zbThing1].[id] = @0))", sql.SQL.NoCrLf());
Assert.AreEqual(1, sql.Arguments.Length);
Assert.AreEqual("foo", sql.Arguments[0]);
sql = template.Sql(123);
Assert.AreEqual(sqlBase + "(([zbThing1].[id] = @0))", sql.SQL.NoCrLf());
Assert.AreEqual(1, sql.Arguments.Length);
Assert.AreEqual(123, sql.Arguments[0]);
// and thanks to a patched visitor, this now works
sql = template.Sql(new { i = "foo" });
Assert.AreEqual(sqlBase + "(([zbThing1].[id] = @0))", sql.SQL.NoCrLf());
Assert.AreEqual(1, sql.Arguments.Length);
Assert.AreEqual("foo", sql.Arguments[0]);
sql = template.Sql(new { i = 123 });
Assert.AreEqual(sqlBase + "(([zbThing1].[id] = @0))", sql.SQL.NoCrLf());
Assert.AreEqual(1, sql.Arguments.Length);
Assert.AreEqual(123, sql.Arguments[0]);
Assert.Throws<InvalidOperationException>(() => template.Sql(new { j = 123 }).WriteToConsole());
Assert.Throws<InvalidOperationException>(() => template.Sql(new { i = 123, j = 456 }).WriteToConsole());
// now with more arguments
template = sqlTemplates.Get("sql4a", s => s.Select<NPocoFetchTests.Thing1Dto>().From<NPocoFetchTests.Thing1Dto>()
.Where<NPocoFetchTests.Thing1Dto>(x => x.Id == SqlTemplate.Arg<int>("i") && x.Name == SqlTemplate.Arg<string>("name")));
sql = template.Sql(0, 1);
Assert.AreEqual(sqlBase + "((([zbThing1].[id] = @0) AND ([zbThing1].[name] = @1)))", sql.SQL.NoCrLf());
Assert.AreEqual(2, sql.Arguments.Length);
Assert.AreEqual(0, sql.Arguments[0]);
Assert.AreEqual(1, sql.Arguments[1]);
template = sqlTemplates.Get("sql4b", s => s.Select<NPocoFetchTests.Thing1Dto>().From<NPocoFetchTests.Thing1Dto>()
.Where<NPocoFetchTests.Thing1Dto>(x => x.Id == SqlTemplate.Arg<int>("i"))
.Where<NPocoFetchTests.Thing1Dto>(x => x.Name == SqlTemplate.Arg<string>("name")));
sql = template.Sql(0, 1);
Assert.AreEqual(sqlBase + "(([zbThing1].[id] = @0)) AND (([zbThing1].[name] = @1))", sql.SQL.NoCrLf());
Assert.AreEqual(2, sql.Arguments.Length);
Assert.AreEqual(0, sql.Arguments[0]);
Assert.AreEqual(1, sql.Arguments[1]);
// works, magic
template = sqlTemplates.Get("sql5", s => s.Select<NPocoFetchTests.Thing1Dto>().From<NPocoFetchTests.Thing1Dto>()
.WhereIn<NPocoFetchTests.Thing1Dto>(x => x.Id, SqlTemplate.ArgIn<int>("i")));
sql = template.Sql("foo");
Assert.AreEqual(sqlBase + "([zbThing1].[id] IN (@0))", sql.SQL.NoCrLf());
Assert.AreEqual(1, sql.Arguments.Length);
Assert.AreEqual("foo", sql.Arguments[0]);
sql = template.Sql(new[] { 1, 2, 3 });
Assert.AreEqual(sqlBase + "([zbThing1].[id] IN (@0,@1,@2))", sql.SQL.NoCrLf());
Assert.AreEqual(3, sql.Arguments.Length);
Assert.AreEqual(1, sql.Arguments[0]);
Assert.AreEqual(2, sql.Arguments[1]);
Assert.AreEqual(3, sql.Arguments[2]);
template = sqlTemplates.Get("sql5a", s => s.Select<NPocoFetchTests.Thing1Dto>().From<NPocoFetchTests.Thing1Dto>()
.WhereIn<NPocoFetchTests.Thing1Dto>(x => x.Id, SqlTemplate.ArgIn<int>("i"))
.Where<NPocoFetchTests.Thing1Dto>(x => x.Name == SqlTemplate.Arg<string>("name")));
sql = template.Sql("foo", "bar");
Assert.AreEqual(sqlBase + "([zbThing1].[id] IN (@0)) AND (([zbThing1].[name] = @1))", sql.SQL.NoCrLf());
Assert.AreEqual(2, sql.Arguments.Length);
Assert.AreEqual("foo", sql.Arguments[0]);
Assert.AreEqual("bar", sql.Arguments[1]);
sql = template.Sql(new[] { 1, 2, 3 }, "bar");
Assert.AreEqual(sqlBase + "([zbThing1].[id] IN (@0,@1,@2)) AND (([zbThing1].[name] = @3))", sql.SQL.NoCrLf());
Assert.AreEqual(4, sql.Arguments.Length);
Assert.AreEqual(1, sql.Arguments[0]);
Assert.AreEqual(2, sql.Arguments[1]);
Assert.AreEqual(3, sql.Arguments[2]);
Assert.AreEqual("bar", sql.Arguments[3]);
// note however that using WhereIn in a template means that the SQL is going
// to be parsed and arguments are going to be expanded etc - it *may* be a better
// idea to just add the WhereIn to a templated, immutable SQL template
// more fun...
template = sqlTemplates.Get("sql6", s => s.Select<NPocoFetchTests.Thing1Dto>().From<NPocoFetchTests.Thing1Dto>()
// do NOT do this, this is NOT a visited expression
//.Append(" AND whatever=@0", SqlTemplate.Arg<string>("j"))
.Append("AND whatever=@0", "j") // instead, directly name the argument
.Append("AND whatever=@0", "k") // same
);
sql = template.Sql(new { j = new[] { 1, 2, 3 }, k = "oops" });
Assert.AreEqual(sqlBase.TrimEnd("WHERE ") + "AND whatever=@0,@1,@2 AND whatever=@3", sql.SQL.NoCrLf());
Assert.AreEqual(4, sql.Arguments.Length);
Assert.AreEqual(1, sql.Arguments[0]);
Assert.AreEqual(2, sql.Arguments[1]);
Assert.AreEqual(3, sql.Arguments[2]);
Assert.AreEqual("oops", sql.Arguments[3]);
}
}
}

View File

@@ -102,7 +102,7 @@ namespace Umbraco.Web.Models.Mapping
//now add the user props
contentProps.AddRange(currProps);
//callback
onGenericPropertiesMapped?.Invoke(contentProps);
@@ -167,7 +167,7 @@ namespace Umbraco.Web.Models.Mapping
var listViewConfig = editor.PreValueEditor.ConvertDbToEditor(editor.DefaultPreValues, preVals);
//add the entity type to the config
listViewConfig["entityType"] = entityType;
//Override Tab Label if tabName is provided
if (listViewConfig.ContainsKey("tabName"))
{

View File

@@ -61,7 +61,7 @@ namespace Umbraco.Web.PropertyEditors
{
[PreValueField("tabName", "Tab Name", "textstring", Description = "The name of the listview tab (default if empty: 'Child Items')")]
public int TabName { get; set; }
[PreValueField("displayAtTabNumber", "Display At Tab Number", "number", Description = "Which tab position that the list of child items will be displayed")]
public int DisplayAtTabNumber { get; set; }

View File

@@ -9,7 +9,7 @@ using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi.Filters;
using Umbraco.Web._Legacy.Actions;
using Constants = Umbraco.Core.Constants;
namespace Umbraco.Web.Trees
{
/// <summary>
@@ -24,23 +24,23 @@ namespace Umbraco.Web.Trees
[CoreTree]
public class ContentBlueprintTreeController : TreeController
{
protected override TreeNode CreateRootNode(FormDataCollection queryStrings)
{
var root = base.CreateRootNode(queryStrings);
//this will load in a custom UI instead of the dashboard for the root node
root.RoutePath = string.Format("{0}/{1}/{2}", Constants.Applications.Settings, Constants.Trees.ContentBlueprints, "intro");
return root;
}
protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings)
{
var nodes = new TreeNodeCollection();
//get all blueprints
var entities = Services.EntityService.GetChildren(Constants.System.Root, UmbracoObjectTypes.DocumentBlueprint).ToArray();
//check if we're rendering the root in which case we'll render the content types that have blueprints
if (id == Constants.System.Root.ToInvariantString())
{
@@ -48,12 +48,12 @@ namespace Umbraco.Web.Trees
var contentTypeAliases = entities.Select(x => ((UmbracoEntity) x).ContentTypeAlias).Distinct();
//get the ids
var contentTypeIds = Services.ContentTypeService.GetAllContentTypeIds(contentTypeAliases.ToArray()).ToArray();
//now get the entities ... it's a bit round about but still smaller queries than getting all document types
var docTypeEntities = contentTypeIds.Length == 0
? new IUmbracoEntity[0]
: Services.EntityService.GetAll(UmbracoObjectTypes.DocumentType, contentTypeIds).ToArray();
nodes.AddRange(docTypeEntities
.Select(entity =>
{
@@ -64,15 +64,15 @@ namespace Umbraco.Web.Trees
treeNode.AdditionalData["jsClickCallback"] = "javascript:void(0);";
return treeNode;
}));
return nodes;
}
var intId = id.TryConvertTo<int>();
//Get the content type
var ct = Services.ContentTypeService.Get(intId.Result);
if (ct == null) return nodes;
var blueprintsForDocType = entities.Where(x => ct.Alias == ((UmbracoEntity) x).ContentTypeAlias);
nodes.AddRange(blueprintsForDocType
.Select(entity =>
@@ -81,14 +81,14 @@ namespace Umbraco.Web.Trees
treeNode.Path = $"-1,{ct.Id},{entity.Id}";
return treeNode;
}));
return nodes;
}
protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
{
var menu = new MenuItemCollection();
if (id == Constants.System.Root.ToInvariantString())
{
// root actions
@@ -103,16 +103,16 @@ namespace Umbraco.Web.Trees
var ct = Services.ContentTypeService.Get(cte.Id);
var createItem = menu.Items.Add<ActionCreateBlueprintFromContent>(Services.TextService.Localize($"actions/{ActionCreateBlueprintFromContent.Instance.Alias}"));
createItem.NavigateToRoute("/settings/contentBlueprints/edit/-1?create=true&doctype=" + ct.Alias);
menu.Items.Add<RefreshNode, ActionRefresh>(Services.TextService.Localize($"actions/{ActionRefresh.Instance.Alias}"), true);
return menu;
}
menu.Items.Add<ActionDelete>(Services.TextService.Localize($"actions/{ActionDelete.Instance.Alias}"));
return menu;
}
}
}