Skip to content

WIP - Support non parameterized constants query plan caching for Linq provider #2375

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

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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
18 changes: 18 additions & 0 deletions src/NHibernate.DomainModel/Northwind/Entities/DynamicUser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Collections;

namespace NHibernate.DomainModel.Northwind.Entities
{
public class DynamicUser : IEnumerable
{
public virtual int Id { get; set; }

public virtual dynamic Properties { get; set; }

public virtual IDictionary Settings { get; set; }

public virtual IEnumerator GetEnumerator()
{
throw new System.NotImplementedException();
}
}
}
5 changes: 5 additions & 0 deletions src/NHibernate.DomainModel/Northwind/Entities/Northwind.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ public IQueryable<User> Users
get { return _session.Query<User>(); }
}

public IQueryable<DynamicUser> DynamicUsers
{
get { return _session.Query<DynamicUser>(); }
}

public IQueryable<PatientRecord> PatientRecords
{
get { return _session.Query<PatientRecord>(); }
Expand Down
6 changes: 6 additions & 0 deletions src/NHibernate.DomainModel/Northwind/Entities/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,16 @@ public class User : IUser, IEntity

public virtual FeatureSet Features { get; set; }

public virtual User NotMappedUser => this;

public virtual EnumStoredAsString Enum1 { get; set; }

public virtual EnumStoredAsString? NullableEnum1 { get; set; }

public virtual EnumStoredAsInt32 Enum2 { get; set; }

public virtual EnumStoredAsInt32? NullableEnum2 { get; set; }

public virtual IUser CreatedBy { get; set; }

public virtual IUser ModifiedBy { get; set; }
Expand Down
30 changes: 30 additions & 0 deletions src/NHibernate.DomainModel/Northwind/Mappings/DynamicUser.hbm.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="NHibernate.DomainModel.Northwind.Entities" assembly="NHibernate.DomainModel">
<class name="DynamicUser" mutable="false">
<subselect>
select * from Users
</subselect>

<id name="Id" column="UserId" type="Int32">
<generator class="assigned" />
</id>

<dynamic-component name="Properties">
<property name="Name" type="AnsiString" />
<property name="Enum1" type="NHibernate.DomainModel.Northwind.Entities.EnumStoredAsStringType, NHibernate.DomainModel">
<column name="Enum1" length="12" />
</property>
<many-to-one name="CreatedBy" class="User" not-null="true" lazy="false">
<column name="CreatedById" not-null="true" />
</many-to-one>
</dynamic-component>

<dynamic-component name="Settings">
<property name="Property1" type="AnsiString" />
<property name="Property2" type="AnsiString" />
<many-to-one name="ModifiedBy" class="User" lazy="false">
<column name="ModifiedById" />
</many-to-one>
</dynamic-component>
</class>
</hibernate-mapping>
6 changes: 6 additions & 0 deletions src/NHibernate.DomainModel/Northwind/Mappings/User.hbm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,14 @@
<column name="Enum1" length="12" />
</property>

<property name="NullableEnum1" type="NHibernate.DomainModel.Northwind.Entities.EnumStoredAsStringType, NHibernate.DomainModel"
formula="(case when Enum1 = 'Unspecified' then null else Enum1 end)" insert="false" update="false">
</property>

<property name="Enum2" not-null="true" />

<property name="NullableEnum2" formula="(case when Enum2 = 0 then null else Enum2 end)" insert="false" update="false" />

<property name="Features" not-null="true" />

<many-to-one name="Role" class="Role">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public void ToFutureValueWithSumOnEmptySetThrowsAsync()
.Select(x => x.Id)
.ToFutureValue(x => x.Sum());

Assert.That(() => personsSum.GetValueAsync(), Throws.InnerException.TypeOf<InvalidOperationException>().Or.InnerException.TypeOf<ArgumentNullException>());
Assert.That(() => personsSum.GetValueAsync(), Throws.TypeOf<InvalidOperationException>().Or.InnerException.TypeOf<ArgumentNullException>());
}
}

Expand Down
12 changes: 6 additions & 6 deletions src/NHibernate.Test/Async/Linq/ConstantTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ public async Task DmlPlansAreCachedAsync()
}

[Test]
public async Task PlansWithNonParameterizedConstantsAreNotCachedAsync()
public async Task PlansWithNonParameterizedConstantsAreCachedAsync()
{
var queryPlanCacheType = typeof(QueryPlanCache);

Expand All @@ -314,12 +314,12 @@ public async Task PlansWithNonParameterizedConstantsAreNotCachedAsync()
select new { c.CustomerId, c.ContactName, Constant = 1 }).FirstAsync());
Assert.That(
cache,
Has.Count.EqualTo(0),
"Query plan should not be cached.");
Has.Count.EqualTo(1),
"Query plan should be cached.");
}

[Test]
public async Task PlansWithNonParameterizedConstantsAreNotCachedForExpandedQueryAsync()
public async Task PlansWithNonParameterizedConstantsAreCachedForExpandedQueryAsync()
{
var queryPlanCacheType = typeof(QueryPlanCache);

Expand All @@ -335,8 +335,8 @@ public async Task PlansWithNonParameterizedConstantsAreNotCachedForExpandedQuery

Assert.That(
cache,
Has.Count.EqualTo(0),
"Query plan should not be cached.");
Has.Count.EqualTo(2), // The second one is for the expanded expression that has two parameters
"Query plan should be cached.");
}

//GH-2298 - Different Update queries - same query cache plan
Expand Down
37 changes: 37 additions & 0 deletions src/NHibernate.Test/Async/Linq/EnumTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,42 @@ public async Task CanQueryOnEnumStoredAsString_Small_1Async()

Assert.AreEqual(expectedCount, query.Count);
}

[Test]
public async Task ConditionalNavigationPropertyAsync()
{
EnumStoredAsString? type = null;
await (db.Users.Where(o => o.Enum1 == EnumStoredAsString.Large).ToListAsync());
await (db.Users.Where(o => EnumStoredAsString.Large != o.Enum1).ToListAsync());
await (db.Users.Where(o => (o.NullableEnum1 ?? EnumStoredAsString.Large) == EnumStoredAsString.Medium).ToListAsync());
await (db.Users.Where(o => ((o.NullableEnum1 ?? type) ?? o.Enum1) == EnumStoredAsString.Medium).ToListAsync());

await (db.Users.Where(o => (o.NullableEnum1.HasValue ? o.Enum1 : EnumStoredAsString.Unspecified) == EnumStoredAsString.Medium).ToListAsync());
await (db.Users.Where(o => (o.Enum1 != EnumStoredAsString.Large
? (o.NullableEnum1.HasValue ? o.Enum1 : EnumStoredAsString.Unspecified)
: EnumStoredAsString.Small) == EnumStoredAsString.Medium).ToListAsync());

await (db.Users.Where(o => (o.Enum1 == EnumStoredAsString.Large ? o.Role : o.Role).Name == "test").ToListAsync());
}

[Test]
public async Task CanQueryComplexExpressionOnEnumStoredAsStringAsync()
{
var type = EnumStoredAsString.Unspecified;
var query = await ((from user in db.Users
where (user.NullableEnum1 == EnumStoredAsString.Large
? EnumStoredAsString.Medium
: user.NullableEnum1 ?? user.Enum1
) == type
select new
{
user,
simple = user.Enum1,
condition = user.Enum1 == EnumStoredAsString.Large ? EnumStoredAsString.Medium : user.Enum1,
coalesce = user.NullableEnum1 ?? EnumStoredAsString.Medium
}).ToListAsync());

Assert.That(query.Count, Is.EqualTo(0));
}
}
}
30 changes: 29 additions & 1 deletion src/NHibernate.Test/Async/Linq/ParameterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,34 @@ public async Task UsingTwoEntityParametersAsync()
2));
}

[Test]
public async Task UsingEntityEnumerableParameterTwiceAsync()
{
if (!Dialect.SupportsSubSelects)
{
Assert.Ignore();
}

var enumerable = await (db.DynamicUsers.FirstAsync());
await (AssertTotalParametersAsync(
db.DynamicUsers.Where(o => o == enumerable && o != enumerable),
1));
}

[Test]
public async Task UsingEntityEnumerableListParameterTwiceAsync()
{
if (!Dialect.SupportsSubSelects)
{
Assert.Ignore();
}

var enumerable = new[] {await (db.DynamicUsers.FirstAsync())};
await (AssertTotalParametersAsync(
db.DynamicUsers.Where(o => enumerable.Contains(o) && enumerable.Contains(o)),
1));
}

[Test]
public async Task UsingValueTypeParameterTwiceAsync()
{
Expand Down Expand Up @@ -322,7 +350,7 @@ public async Task UsingTwoParametersInDMLDeleteAsync()
{
// In case of arrays linqParameterNumber and parameterNumber will be different
Assert.That(
GetLinqExpression(query).ParameterValuesByName.Count,
GetLinqExpression(query).NamedParameters.Count,
Is.EqualTo(linqParameterNumber ?? parameterNumber),
"Linq expression has different number of parameters");

Expand Down
Loading