Skip to content

NH-3489 improve GetEffectiveParameterLocations performance #214

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 22, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/NH3489/Department.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Collections.Generic;

namespace NHibernate.Test.NHSpecificTest.NH3489
{
public class Department
{
public Department()
{
Orders = new HashSet<Order>();
}

public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ISet<Order> Orders { get; set; }
}
}
123 changes: 123 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/NH3489/Fixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using NHibernate.Cfg;
using NHibernate.Cfg.MappingSchema;
using NHibernate.Mapping.ByCode;
using NUnit.Framework;
using Environment = NHibernate.Cfg.Environment;

namespace NHibernate.Test.NHSpecificTest.NH3489
{
[TestFixture]
[Ignore("Only run to test performance.")]
public class Fixture : TestCaseMappingByCode
{
private const int batchSize = 900;

protected override HbmMapping GetMappings()
{
var mapper = new ModelMapper();
mapper.Class<Order>(cm =>
{
cm.Id(x => x.Id, m => m.Generator(Generators.HighLow));
cm.Table("Orders");
cm.Property(x => x.Name);
cm.Set(x => x.Departments, m =>
{
m.Table("Orders_Departments");
m.Key(k =>
{
k.Column("OrderId");
k.NotNullable(true);
});
m.BatchSize(batchSize);
m.Cascade(Mapping.ByCode.Cascade.All);
}, r => r.ManyToMany(c => c.Column("DepartmentId")));
});

mapper.Class<Department>(cm =>
{
cm.Id(x => x.Id, m => m.Generator(Generators.HighLow));
cm.Table("Departments");
cm.Set(x => x.Orders, m =>
{
m.Table("Orders_Departments");
m.Key(k =>
{
k.Column("DepartmentId");
k.NotNullable(true);
});
m.BatchSize(batchSize);
m.Cascade(Mapping.ByCode.Cascade.All);
m.Inverse(true);
}, r => r.ManyToMany(c => c.Column("OrderId")));
});

return mapper.CompileMappingForAllExplicitlyAddedEntities();
}

protected override void Configure(Configuration configuration)
{
base.Configure(configuration);
configuration.SetProperty(Environment.BatchSize, batchSize.ToString(CultureInfo.InvariantCulture));
}

protected override void OnSetUp()
{
using (ISession session = OpenSession())
using (ITransaction transaction = session.BeginTransaction())
{
var department1 = new Department {Name = "Dep 1"};
session.Save(department1);
var department2 = new Department {Name = "Dep 2"};
session.Save(department2);

for (int i = 0; i < 10000; i++)
{
var order = new Order {Name = "Order " + (i + 1)};
order.Departments.Add(department1);
order.Departments.Add(department2);
session.Save(order);
}

session.Flush();
transaction.Commit();
}
}

protected override void OnTearDown()
{
using (ISession session = OpenSession())
using (ITransaction transaction = session.BeginTransaction())
{
session.Delete("from System.Object");

session.Flush();
transaction.Commit();
}
}

protected override string CacheConcurrencyStrategy
{
get { return null; }
}

[Test]
public void PerformanceTest()
{
Stopwatch stopwatch = Stopwatch.StartNew();
using (ISession session = OpenSession())
using (session.BeginTransaction())
{
IList<Order> orders = session.QueryOver<Order>().List();
foreach (Order order in orders)
order.Departments.ToList();
}
stopwatch.Stop();
Console.WriteLine(stopwatch.Elapsed);
}
}
}
17 changes: 17 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/NH3489/Order.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Collections.Generic;

namespace NHibernate.Test.NHSpecificTest.NH3489
{
public class Order
{
public Order()
{
Departments = new HashSet<Department>();
}

public virtual int Id { get; set; }
public virtual string Name { get; set; }

public virtual ISet<Department> Departments { get; set; }
}
}
3 changes: 3 additions & 0 deletions src/NHibernate.Test/NHibernate.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,9 @@
<Compile Include="NHSpecificTest\NH1082\SessionInterceptorThatThrowsExceptionAtBeforeTransactionCompletion.cs" />
<Compile Include="NHSpecificTest\NH3571\Fixture.cs" />
<Compile Include="NHSpecificTest\NH3571\Product.cs" />
<Compile Include="NHSpecificTest\NH3489\Department.cs" />
<Compile Include="NHSpecificTest\NH3489\Order.cs" />
<Compile Include="NHSpecificTest\NH3489\Fixture.cs" />
<Compile Include="NHSpecificTest\NH3459\Fixture.cs" />
<Compile Include="NHSpecificTest\NH3459\Order.cs" />
<Compile Include="NHSpecificTest\NH2692\Fixture.cs" />
Expand Down
1 change: 1 addition & 0 deletions src/NHibernate/NHibernate.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,7 @@
<Compile Include="NonUniqueResultException.cs" />
<Compile Include="ObjectDeletedException.cs" />
<Compile Include="ObjectNotFoundException.cs" />
<Compile Include="SqlCommand\BackTrackCacheParameterList.cs" />
<Compile Include="Param\CriteriaNamedParameterSpecification.cs" />
<Compile Include="Param\IPageableParameterSpecification.cs" />
<Compile Include="Param\ParametersBackTrackExtensions.cs" />
Expand Down
18 changes: 15 additions & 3 deletions src/NHibernate/Param/ParametersBackTrackExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,28 @@ public static class ParametersBackTrackExtensions
{
public static IEnumerable<int> GetEffectiveParameterLocations(this IList<Parameter> sqlParameters, string backTrackId)
{
var cacheParameters = sqlParameters as BackTrackCacheParameterList;
return cacheParameters != null
? cacheParameters.GetEffectiveParameterLocations(backTrackId)
: GetEffectiveParameterLocationsSlow(sqlParameters, backTrackId);
}

private static IEnumerable<int> GetEffectiveParameterLocationsSlow(IList<Parameter> sqlParameters, string backTrackId) {
for (int i = 0; i < sqlParameters.Count; i++)
{
if (backTrackId.Equals(sqlParameters[i].BackTrack))
{
yield return i;
}
}
}

public static SqlType[] GetQueryParameterTypes(this IEnumerable<IParameterSpecification> parameterSpecs, List<Parameter> sqlQueryParametersList, ISessionFactoryImplementor factory)
internal static BackTrackCacheParameterList ToBackTrackCacheParameterList(this IEnumerable<Parameter> sqlParameters) {
var list = new BackTrackCacheParameterList();
foreach (Parameter sqlParameter in sqlParameters)
list.Add(sqlParameter);
return list;
}

public static SqlType[] GetQueryParameterTypes(this IEnumerable<IParameterSpecification> parameterSpecs, IList<Parameter> sqlQueryParametersList, ISessionFactoryImplementor factory)
{
// NOTE: if you have a NullReferenceException probably is because the IParameterSpecification does not have the ExpectedType; use ResetEffectiveExpectedType before call this method.

Expand Down
104 changes: 104 additions & 0 deletions src/NHibernate/SqlCommand/BackTrackCacheParameterList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using NHibernate.Util;

namespace NHibernate.SqlCommand
{
/// <summary>
/// A list of <see cref="Parameter"/> that maintains a cache of backtrace positions for performance purpose.
/// See https://nhibernate.jira.com/browse/NH-3489.
/// </summary>
internal class BackTrackCacheParameterList : Collection<Parameter>
{
private Dictionary<string, SortedSet<int>> _indexesByBackTrace;

private void AddIndex(Parameter parameter, int index)
{
var backTrack = parameter.BackTrack as string;
if (backTrack == null)
return;

SortedSet<int> indexes;
if (!_indexesByBackTrace.TryGetValue(backTrack, out indexes))
{
indexes = new SortedSet<int>();
_indexesByBackTrace.Add(backTrack, indexes);
}
indexes.Add(index);
}

private void RemoveIndexes(Parameter parameter)
{
var backTrack = parameter.BackTrack as string;
if (backTrack != null)
_indexesByBackTrace.Remove(backTrack);
}

private Dictionary<string, SortedSet<int>> BuildBackTrackCache()
{
var indexesByBackTrace = new Dictionary<string, SortedSet<int>>();
IList<Parameter> parameters = Items;
for (int i = 0; i < parameters.Count; i++)
{
var backTrace = parameters[i].BackTrack as string;
if (backTrace != null)
{
SortedSet<int> locations;
if (!indexesByBackTrace.TryGetValue(backTrace, out locations))
{
locations = new SortedSet<int>();
indexesByBackTrace.Add(backTrace, locations);
}
locations.Add(i);
}
}
return indexesByBackTrace;
}

protected override void InsertItem(int index, Parameter item)
{
base.InsertItem(index, item);
if (_indexesByBackTrace != null)
AddIndex(item, index);
}

protected override void RemoveItem(int index)
{
Parameter oldItem = Items[index];
base.RemoveItem(index);
if (_indexesByBackTrace != null)
RemoveIndexes(oldItem);
}

protected override void SetItem(int index, Parameter item)
{
Parameter oldItem = Items[index];
base.SetItem(index, item);
if (_indexesByBackTrace != null)
{
RemoveIndexes(oldItem);
AddIndex(item, index);
}
}

protected override void ClearItems()
{
base.ClearItems();
if (_indexesByBackTrace != null)
_indexesByBackTrace.Clear();
}

public IEnumerable<int> GetEffectiveParameterLocations(string backTrace)
{
if (backTrace != null)
{
if (_indexesByBackTrace == null)
_indexesByBackTrace = BuildBackTrackCache();
SortedSet<int> indexes;
if (_indexesByBackTrace.TryGetValue(backTrace, out indexes))
return indexes;
}
return ArrayHelper.EmptyIntArray;
}
}
}
9 changes: 5 additions & 4 deletions src/NHibernate/SqlCommand/SqlCommandImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public class SqlCommandImpl : ISqlCommand
private readonly QueryParameters queryParameters;
private readonly ISessionFactoryImplementor factory;
private SqlType[] parameterTypes;
List<Parameter> sqlQueryParametersList;
IList<Parameter> sqlQueryParametersList;

public SqlCommandImpl(SqlString query, ICollection<IParameterSpecification> specifications, QueryParameters queryParameters, ISessionFactoryImplementor factory)
{
Expand All @@ -70,9 +70,9 @@ public SqlCommandImpl(SqlString query, ICollection<IParameterSpecification> spec
this.factory = factory;
}

public List<Parameter> SqlQueryParametersList
public IList<Parameter> SqlQueryParametersList
{
get { return sqlQueryParametersList ?? (sqlQueryParametersList = query.GetParameters().ToList()); }
get { return sqlQueryParametersList ?? (sqlQueryParametersList = query.GetParameters().ToBackTrackCacheParameterList()); }
}

public SqlType[] ParameterTypes
Expand Down Expand Up @@ -103,6 +103,7 @@ public void ResetParametersIndexesForTheCommand(int singleSqlParametersOffset)
{
throw new AssertionFailure("singleSqlParametersOffset < 0 - this indicate a bug in NHibernate ");
}

// due to IType.NullSafeSet(System.Data.IDbCommand , object, int, ISessionImplementor) the SqlType[] is supposed to be in a certain sequence.
// this mean that found the first location of a parameter for the IType span, the others are in secuence
foreach (IParameterSpecification specification in Specifications)
Expand All @@ -111,7 +112,7 @@ public void ResetParametersIndexesForTheCommand(int singleSqlParametersOffset)
int[] effectiveParameterLocations = SqlQueryParametersList.GetEffectiveParameterLocations(firstParameterId).ToArray();
if (effectiveParameterLocations.Length > 0) // Parameters previously present might have been removed from the SQL at a later point.
{
int firstParamNameIndex = effectiveParameterLocations.First() + singleSqlParametersOffset;
int firstParamNameIndex = effectiveParameterLocations[0] + singleSqlParametersOffset;
foreach (int location in effectiveParameterLocations)
{
int parameterSpan = specification.ExpectedType.GetColumnSpan(factory);
Expand Down