Skip to content

Commit 0ae9ca6

Browse files
committed
Draft for EntityProjections
1 parent efae48b commit 0ae9ca6

File tree

6 files changed

+649
-9
lines changed

6 files changed

+649
-9
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace NHibernate.Test.NHSpecificTest.GH948
5+
{
6+
public class EntitySimpleChild
7+
{
8+
public virtual Guid Id { get; set; }
9+
public virtual string Name { get; set; }
10+
}
11+
12+
public class EntityComplex
13+
{
14+
public virtual Guid Id { get; set; }
15+
16+
public virtual int Version { get; set; }
17+
18+
public virtual string Name { get; set; }
19+
20+
public virtual string LazyProp { get; set; }
21+
22+
public virtual EntitySimpleChild Child1 { get; set; }
23+
public virtual EntitySimpleChild Child2 { get; set; }
24+
public virtual EntityComplex SameTypeChild { get; set; }
25+
26+
public virtual IList<EntitySimpleChild> ChildrenList { get; set; }
27+
}
28+
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
using System;
2+
using NHibernate.Criterion;
3+
using NHibernate.Engine;
4+
using NHibernate.SqlCommand;
5+
using NHibernate.Type;
6+
using System.Linq;
7+
using IQueryable = NHibernate.Persister.Entity.IQueryable;
8+
9+
namespace NHibernate.Test.NHSpecificTest.GH948
10+
{
11+
using TThis = EntityProjection;
12+
13+
[Serializable]
14+
public class EntityProjection : IProjection
15+
{
16+
private string _entityAlias;
17+
private string _columnAliasSuffix;
18+
public string _tableAlias;
19+
private IType[] _types;
20+
21+
/// <summary>
22+
/// Root entity projection
23+
/// </summary>
24+
public EntityProjection():this(null, null)
25+
{}
26+
27+
public EntityProjection(System.Type rootEntity, string entityAlias)
28+
{
29+
RootEntity = rootEntity;
30+
_entityAlias = entityAlias;
31+
}
32+
33+
public bool FetchLazyProperties { get; set; }
34+
public bool IsReadOnly { get; set; }
35+
public bool Lazy { get; set; }
36+
37+
internal string[] IdentifierAliases { get; private set; }
38+
internal string[][] PropertyAliases { get; private set; }
39+
internal IQueryable Persister { get; private set; }
40+
internal System.Type RootEntity { get; private set; }
41+
42+
#region Configuration methods
43+
44+
public TThis SetLazy(bool lazy = true)
45+
{
46+
Lazy = lazy;
47+
return this;
48+
}
49+
50+
public TThis SetAllPropertyFetch(bool fetchLazyProperties = true)
51+
{
52+
FetchLazyProperties = fetchLazyProperties;
53+
return this;
54+
}
55+
56+
public TThis SetReadonly(bool isReadOnly = true)
57+
{
58+
IsReadOnly = isReadOnly;
59+
return this;
60+
}
61+
62+
#endregion Configuration methods
63+
64+
#region IProjection implementation
65+
66+
public string[] Aliases
67+
{
68+
get;
69+
private set;
70+
}
71+
72+
bool IProjection.IsAggregate
73+
{
74+
get { return false; }
75+
}
76+
77+
bool IProjection.IsGrouped
78+
{
79+
get { return false; }
80+
}
81+
82+
IType[] IProjection.GetTypes(string alias, ICriteria criteria, ICriteriaQuery criteriaQuery)
83+
{
84+
return new[] { criteriaQuery.GetType(criteria, alias) };
85+
}
86+
87+
IType[] IProjection.GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery)
88+
{
89+
SetFields(criteria, criteriaQuery);
90+
return _types;
91+
}
92+
93+
public string[] GetColumnAliases(int position, ICriteria criteria, ICriteriaQuery criteriaQuery)
94+
{
95+
SetFields(criteria, criteriaQuery);
96+
return Aliases;
97+
}
98+
99+
public string[] GetColumnAliases(string alias, int position, ICriteria criteria, ICriteriaQuery criteriaQuery)
100+
{
101+
SetFields(criteria, criteriaQuery);
102+
return Aliases;
103+
}
104+
105+
SqlString IProjection.ToSqlString(ICriteria criteria, int position, ICriteriaQuery criteriaQuery)
106+
{
107+
SetFields(criteria, criteriaQuery);
108+
109+
string identifierSelectFragment = Persister.IdentifierSelectFragment(_tableAlias, _columnAliasSuffix);
110+
if (Lazy)
111+
return new SqlString(identifierSelectFragment);
112+
113+
return new SqlString(
114+
string.Concat(
115+
identifierSelectFragment,
116+
Persister.PropertySelectFragment(_tableAlias, _columnAliasSuffix, FetchLazyProperties)));
117+
}
118+
119+
SqlString IProjection.ToGroupSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery)
120+
{
121+
throw new NotImplementedException();
122+
}
123+
124+
TypedValue[] IProjection.GetTypedValues(ICriteria criteria, ICriteriaQuery criteriaQuery)
125+
{
126+
throw new NotImplementedException();
127+
}
128+
129+
#endregion IProjection implementation
130+
131+
private void SetFields(ICriteria criteria, ICriteriaQuery criteriaQuery)
132+
{
133+
//Persister is required, so let's use it as "initialized marker"
134+
if (Persister != null)
135+
return;
136+
137+
if (RootEntity == null)
138+
{
139+
RootEntity = criteria.GetRootEntityTypeIfAvailable();
140+
}
141+
142+
if (_entityAlias == null)
143+
{
144+
_entityAlias = criteria.Alias;
145+
}
146+
147+
Persister = (IQueryable) criteriaQuery.Factory.GetEntityPersister(RootEntity.FullName);
148+
149+
ICriteria subcriteria = criteria.GetCriteriaByAlias(_entityAlias);
150+
if (subcriteria == null)
151+
throw new HibernateException($"Criteria\\QueryOver alias '{_entityAlias}' for entity projection is not found.");
152+
153+
_tableAlias = criteriaQuery.GetSQLAlias(
154+
subcriteria,
155+
Persister.IdentifierPropertyName ?? string.Empty);
156+
157+
//let's simply use table aliases as column alias suffixes. For SQL Server it will look something like: tableAlias4_.Id AS id1_1_tableAlias4_
158+
_columnAliasSuffix = _tableAlias;
159+
160+
_types = new IType[] {new EntityProjectionType(this)};
161+
162+
IdentifierAliases = Persister.GetIdentifierAliases(_columnAliasSuffix);
163+
164+
PropertyAliases = Lazy
165+
? new string[][] { }
166+
: Enumerable.Range(0, Persister.PropertyNames.Length).Select(i => Persister.GetPropertyAliases(_columnAliasSuffix, i)).ToArray();
167+
168+
Aliases = IdentifierAliases.Concat(PropertyAliases.SelectMany(alias => alias)).ToArray();
169+
}
170+
}
171+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
using System;
2+
using System.Data.Common;
3+
using NHibernate.Engine;
4+
using NHibernate.Event;
5+
using NHibernate.Persister.Entity;
6+
using NHibernate.Type;
7+
8+
namespace NHibernate.Test.NHSpecificTest.GH948
9+
{
10+
/// <summary>
11+
/// Specialized type for retrieving entity from Criteria/QueryOver API projection.
12+
/// Intended to be used only inside <see cref="EntityProjection"/>
13+
/// </summary>
14+
[Serializable]
15+
internal partial class EntityProjectionType : ManyToOneType
16+
{
17+
private readonly EntityProjection _projection;
18+
19+
public EntityProjectionType(EntityProjection projection) : base(projection.RootEntity.FullName, projection.Lazy)
20+
{
21+
_projection = projection;
22+
}
23+
24+
public override object NullSafeGet(DbDataReader rs, string name, ISessionImplementor session, object owner)
25+
{
26+
if (_projection.Lazy)
27+
return base.NullSafeGet(rs, name, session, owner);
28+
29+
return GetInitializedEntityFromProjection(rs, session);
30+
}
31+
32+
private object GetInitializedEntityFromProjection(DbDataReader rs, ISessionImplementor session)
33+
{
34+
var identifier = _projection.Persister.IdentifierType.NullSafeGet(rs, _projection.IdentifierAliases, session, null);
35+
if (identifier == null)
36+
{
37+
return null;
38+
}
39+
40+
var entity = CreateInitializedEntity(
41+
rs,
42+
session,
43+
_projection.Persister,
44+
identifier,
45+
_projection.PropertyAliases,
46+
LockMode.None,
47+
_projection.FetchLazyProperties,
48+
_projection.IsReadOnly);
49+
50+
return entity;
51+
}
52+
53+
private static object CreateInitializedEntity(DbDataReader rs, ISessionImplementor session, IQueryable persister, object identifier, string[][] propertyAliases, LockMode lockMode, bool fetchLazyProperties, bool readOnly)
54+
{
55+
var eventSource = session as IEventSource;
56+
PostLoadEvent postLoadEvent = null;
57+
PreLoadEvent preLoadEvent = null;
58+
object entity;
59+
if (eventSource != null)
60+
{
61+
preLoadEvent = new PreLoadEvent(eventSource);
62+
postLoadEvent = new PostLoadEvent(eventSource);
63+
entity = eventSource.Instantiate(persister, identifier);
64+
}
65+
else
66+
{
67+
entity = session.Instantiate(persister.EntityName, identifier);
68+
}
69+
70+
TwoPhaseLoad.AddUninitializedEntity(
71+
session.GenerateEntityKey(identifier, persister),
72+
entity,
73+
persister,
74+
lockMode,
75+
!fetchLazyProperties,
76+
session);
77+
78+
var hydrated = persister.Hydrate(
79+
rs,
80+
identifier,
81+
entity,
82+
persister,
83+
propertyAliases,
84+
fetchLazyProperties,
85+
session);
86+
87+
TwoPhaseLoad.PostHydrate(persister, identifier, hydrated, null, entity, lockMode, !fetchLazyProperties, session);
88+
TwoPhaseLoad.InitializeEntity(entity, readOnly, session, preLoadEvent, postLoadEvent);
89+
return entity;
90+
}
91+
}
92+
}

0 commit comments

Comments
 (0)