Skip to content

Commit dc008a6

Browse files
committed
NH3480 - Initial proof-of-concept fix
1 parent 334f3c8 commit dc008a6

File tree

7 files changed

+138
-58
lines changed

7 files changed

+138
-58
lines changed

src/NHibernate/Engine/JoinHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public static string[] GetRHSColumnNames(IAssociationType type, ISessionFactoryI
3131
IJoinable joinable = type.GetAssociatedJoinable(factory);
3232
if (uniqueKeyPropertyName == null)
3333
{
34-
return joinable.KeyColumnNames;
34+
return joinable.JoinColumnNames;
3535
}
3636
else
3737
{

src/NHibernate/Loader/Collection/OneToManyJoinWalker.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Collections.Generic;
23
using NHibernate.Engine;
34
using NHibernate.Persister.Collection;
@@ -57,7 +58,7 @@ private void InitStatementString(IOuterJoinLoadable elementPersister, string ali
5758
int collectionJoins = CountCollectionPersisters(associations) + 1;
5859
CollectionSuffixes = BasicLoader.GenerateSuffixes(joins + 1, collectionJoins);
5960

60-
SqlStringBuilder whereString = WhereString(alias, oneToManyPersister.KeyColumnNames, subquery, batchSize);
61+
SqlStringBuilder whereString = WhereString(oneToManyPersister.CollectionType.UseLHSPrimaryKey ? alias : alias + "owner_", oneToManyPersister.KeyColumnNames, subquery, batchSize);
6162
string filter = oneToManyPersister.FilterFragment(alias, EnabledFilters);
6263
whereString.Insert(0, StringHelper.MoveAndToBeginning(filter));
6364

@@ -66,7 +67,7 @@ private void InitStatementString(IOuterJoinLoadable elementPersister, string ali
6667
new SqlSelectBuilder(Factory).SetSelectClause(
6768
oneToManyPersister.SelectFragment(null, null, alias, Suffixes[joins], CollectionSuffixes[0], true)
6869
+ SelectString(associations)).SetFromClause(elementPersister.FromTableFragment(alias)
69-
+ elementPersister.FromJoinFragment(alias, true, true)).SetWhereClause(
70+
+ oneToManyPersister.FromJoinFragment(alias, true, true)).SetWhereClause(
7071
whereString.ToSqlString()).SetOuterJoins(ojf.ToFromFragmentString,
7172
ojf.ToWhereFragmentString
7273
+ elementPersister.WhereJoinFragment(alias, true, true));

src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public abstract class AbstractCollectionPersister : ICollectionMetadata, ISqlLoa
8787
private readonly string[] keyColumnAliases;
8888
private readonly string identifierColumnName;
8989
private readonly string identifierColumnAlias;
90+
private readonly string[] joinColumnNames;
9091

9192
#endregion
9293

@@ -228,17 +229,43 @@ public AbstractCollectionPersister(Mapping.Collection collection, ICacheConcurre
228229

229230
isVersioned = collection.IsOptimisticLocked;
230231

231-
keyType = collection.Key.Type;
232-
int keySpan = collection.Key.ColumnSpan;
233-
keyColumnNames = new string[keySpan];
234-
keyColumnAliases = new string[keySpan];
235-
int k = 0;
236-
foreach (Column col in collection.Key.ColumnIterator)
232+
if (collection.CollectionType.UseLHSPrimaryKey)
237233
{
238-
keyColumnNames[k] = col.GetQuotedName(dialect);
239-
keyColumnAliases[k] = col.GetAlias(dialect);
240-
k++;
234+
keyType = collection.Key.Type;
235+
int keySpan = collection.Key.ColumnSpan;
236+
keyColumnNames = new string[keySpan];
237+
keyColumnAliases = new string[keySpan];
238+
int k = 0;
239+
foreach (Column col in collection.Key.ColumnIterator)
240+
{
241+
keyColumnNames[k] = col.GetQuotedName(dialect);
242+
keyColumnAliases[k] = col.GetAlias(dialect);
243+
k++;
244+
}
245+
joinColumnNames = keyColumnNames;
241246
}
247+
else
248+
{
249+
keyType = collection.Owner.Key.Type;
250+
int keySpan = collection.Owner.Key.ColumnSpan;
251+
keyColumnNames = new string[keySpan];
252+
keyColumnAliases = new string[keySpan];
253+
int k = 0;
254+
foreach (Column col in collection.Owner.Key.ColumnIterator)
255+
{
256+
keyColumnNames[k] = col.GetQuotedName(dialect);
257+
keyColumnAliases[k] = col.GetAlias(dialect) + "_owner_";
258+
k++;
259+
}
260+
joinColumnNames = new string[collection.Key.ColumnSpan];
261+
k = 0;
262+
foreach (Column col in collection.Key.ColumnIterator)
263+
{
264+
joinColumnNames[k] = col.GetQuotedName(dialect);
265+
k++;
266+
}
267+
}
268+
242269
HashSet<string> distinctColumns = new HashSet<string>();
243270
CheckColumnDuplication(distinctColumns, collection.Key.ColumnIterator);
244271

@@ -1763,6 +1790,11 @@ public string[] KeyColumnNames
17631790
get { return keyColumnNames; }
17641791
}
17651792

1793+
public string[] JoinColumnNames
1794+
{
1795+
get { return joinColumnNames; }
1796+
}
1797+
17661798
protected string[] KeyColumnAliases
17671799
{
17681800
get { return keyColumnAliases; }

src/NHibernate/Persister/Collection/OneToManyPersister.cs

Lines changed: 80 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -64,21 +64,69 @@ public override bool IsManyToMany
6464
/// <returns></returns>
6565
protected override SqlCommandInfo GenerateDeleteString()
6666
{
67-
var update = new SqlUpdateBuilder(Factory.Dialect, Factory)
68-
.SetTableName(qualifiedTableName)
69-
.AddColumns(KeyColumnNames, "null")
70-
.SetIdentityColumn(KeyColumnNames, KeyType);
67+
if (CollectionType.UseLHSPrimaryKey)
68+
{
69+
var update = new SqlUpdateBuilder(Factory.Dialect, Factory)
70+
.SetTableName(qualifiedTableName)
71+
.AddColumns(KeyColumnNames, "null")
72+
.SetIdentityColumn(KeyColumnNames, KeyType);
7173

72-
if (HasIndex)
73-
update.AddColumns(IndexColumnNames, "null");
74+
if (HasIndex)
75+
update.AddColumns(IndexColumnNames, "null");
7476

75-
if (HasWhere)
76-
update.AddWhereFragment(sqlWhereString);
77+
if (HasWhere)
78+
update.AddWhereFragment(sqlWhereString);
7779

78-
if (Factory.Settings.IsCommentsEnabled)
79-
update.SetComment("delete one-to-many " + Role);
80+
if (Factory.Settings.IsCommentsEnabled)
81+
update.SetComment("delete one-to-many " + Role);
8082

81-
return update.ToSqlCommandInfo();
83+
return update.ToSqlCommandInfo();
84+
}
85+
else
86+
{
87+
var deleteSql = new SqlStringBuilder();
88+
89+
deleteSql.Add("update " + qualifiedTableName + " set ");
90+
bool andNeeded = false;
91+
foreach (string nextColumn in JoinColumnNames)
92+
{
93+
if (andNeeded)
94+
{
95+
deleteSql.Add(" and ");
96+
}
97+
deleteSql.Add(nextColumn + " = null");
98+
andNeeded = true;
99+
}
100+
101+
var ownerPersister = (IOuterJoinLoadable)OwnerEntityPersister;
102+
deleteSql.Add(" from " + qualifiedTableName + " inner join " + ownerPersister.TableName + " on ");
103+
var ownerJoinColumns = ownerPersister.GetPropertyColumnNames(CollectionType.LHSPropertyName);
104+
andNeeded = false;
105+
for (int columnIndex = 0; columnIndex < JoinColumnNames.Length; columnIndex++)
106+
{
107+
if (andNeeded)
108+
{
109+
deleteSql.Add(" and ");
110+
}
111+
deleteSql.Add(qualifiedTableName + StringHelper.Dot + JoinColumnNames[columnIndex] + " = " + ownerPersister.TableName + StringHelper.Dot + ownerJoinColumns[columnIndex]);
112+
andNeeded = true;
113+
}
114+
115+
deleteSql.Add(" where ");
116+
andNeeded = false;
117+
foreach (string nextColumn in KeyColumnNames)
118+
{
119+
if (andNeeded)
120+
{
121+
deleteSql.Add(" and ");
122+
}
123+
deleteSql.Add(ownerPersister.TableName + StringHelper.Dot + nextColumn + " = ");
124+
deleteSql.AddParameter();
125+
andNeeded = true;
126+
}
127+
128+
return new SqlCommandInfo(deleteSql.ToSqlString(), KeyType.SqlTypes(Factory));
129+
}
82130
}
83131

84132
/// <summary>
@@ -320,7 +368,7 @@ protected override SelectFragment GenerateSelectFragment(string alias, string co
320368
for (int i = 0; i < columnNames.Length; i++)
321369
{
322370
var column = columnNames[i];
323-
var tableAlias = ojl.GenerateTableAliasForColumn(alias, column);
371+
var tableAlias = ojl.GenerateTableAliasForColumn(alias + (CollectionType.UseLHSPrimaryKey ? String.Empty : "owner_"), column);
324372
selectFragment.AddColumn(tableAlias, column, columnAliases[i]);
325373
}
326374
return selectFragment;
@@ -336,7 +384,26 @@ protected override ICollectionInitializer CreateCollectionInitializer(IDictionar
336384

337385
public override SqlString FromJoinFragment(string alias, bool innerJoin, bool includeSubclasses)
338386
{
339-
return ((IJoinable)ElementPersister).FromJoinFragment(alias, innerJoin, includeSubclasses);
387+
var elementJoinFragment = ((IJoinable)ElementPersister).FromJoinFragment(alias, innerJoin, includeSubclasses);
388+
389+
if (CollectionType.UseLHSPrimaryKey)
390+
{
391+
return elementJoinFragment;
392+
}
393+
394+
JoinFragment join = Factory.Dialect.CreateOuterJoinFragment();
395+
396+
var lhsKeyColumnNames = new string[JoinColumnNames.Length];
397+
int k = 0;
398+
foreach (string columnName in JoinColumnNames)
399+
{
400+
lhsKeyColumnNames[k] = alias + StringHelper.Dot + columnName;
401+
k++;
402+
}
403+
404+
var ownerPersister = (IOuterJoinLoadable)OwnerEntityPersister;
405+
join.AddJoin(ownerPersister.TableName, alias + "owner_", lhsKeyColumnNames, ownerPersister.GetPropertyColumnNames(CollectionType.LHSPropertyName), JoinType.LeftOuterJoin);
406+
return join.ToFromFragmentString + elementJoinFragment;
340407
}
341408

342409
public override SqlString WhereJoinFragment(string alias, bool innerJoin, bool includeSubclasses)

src/NHibernate/Persister/Entity/AbstractEntityPersister.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,11 @@ public string[] KeyColumnNames
878878
get { return IdentifierColumnNames; }
879879
}
880880

881+
public string[] JoinColumnNames
882+
{
883+
get { return KeyColumnNames; }
884+
}
885+
881886
public string Name
882887
{
883888
get { return EntityName; }

src/NHibernate/Persister/Entity/IJoinable.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,15 @@ public interface IJoinable
1616
string Name { get; }
1717

1818
/// <summary>
19-
/// The columns to join on.
19+
/// The columns that identify the item.
2020
/// </summary>
2121
string[] KeyColumnNames { get; }
2222

23+
/// <summary>
24+
/// The columns to join on.
25+
/// </summary>
26+
string[] JoinColumnNames { get; }
27+
2328
/// <summary>
2429
/// Is this instance actually a ICollectionPersister?
2530
/// </summary>

src/NHibernate/Type/CollectionType.cs

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -467,8 +467,7 @@ public override bool IsModified(object oldHydratedState, object currentState, bo
467467
}
468468

469469
/// <summary>
470-
/// Get the key value from the owning entity instance, usually the identifier, but might be some
471-
/// other unique key, in the case of property-ref
470+
/// Get the key value from the owning entity instance.
472471
/// </summary>
473472
public object GetKeyOfOwner(object owner, ISessionImplementor session)
474473
{
@@ -477,36 +476,7 @@ public object GetKeyOfOwner(object owner, ISessionImplementor session)
477476
return null; // This just handles a particular case of component
478477
// projection, perhaps get rid of it and throw an exception
479478

480-
if (foreignKeyPropertyName == null)
481-
{
482-
return entityEntry.Id;
483-
}
484-
else
485-
{
486-
// TODO: at the point where we are resolving collection references, we don't
487-
// know if the uk value has been resolved (depends if it was earlier or
488-
// later in the mapping document) - now, we could try and use e.getStatus()
489-
// to decide to semiResolve(), trouble is that initializeEntity() reuses
490-
// the same array for resolved and hydrated values
491-
object id;
492-
if (entityEntry.LoadedState != null)
493-
{
494-
id = entityEntry.GetLoadedValue(foreignKeyPropertyName);
495-
}
496-
else
497-
{
498-
id = entityEntry.Persister.GetPropertyValue(owner, foreignKeyPropertyName, session.EntityMode);
499-
}
500-
501-
// NOTE VERY HACKISH WORKAROUND!!
502-
IType keyType = GetPersister(session).KeyType;
503-
if (!keyType.ReturnedClass.IsInstanceOfType(id))
504-
{
505-
id = keyType.SemiResolve(entityEntry.GetLoadedValue(foreignKeyPropertyName), session, owner);
506-
}
507-
508-
return id;
509-
}
479+
return entityEntry.Id;
510480
}
511481

512482
/// <summary>

0 commit comments

Comments
 (0)