-
Notifications
You must be signed in to change notification settings - Fork 921
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
NH-3670 - Dynamic component should allow generic dictionary #305
Conversation
Can you check if the reference documentation section on this would benefit from an update regarding this? Should we deprecate the use of non-generic dictionary in this context and log an issue for NH5.0 to remove support for it? |
@rjperes could you investigate the failure? |
Will do |
Also, please rebase on top of master as @oskarb pushed line endings changes. |
Fixed problem. Need some help with the rebasing part |
In git command line: You will get conflicts. I use TortoiseGit to resolve:
If all is well you can now force push the new branch, which I believe should just replace the commits in this PR with your new rebased commits. |
Don't we have any tests outside of the NHSpecificTest directory as basic feature tests for dynamic components? There should be generic versions added to them. |
Created unit tests for classes MappingByCode\MappersTests\DynamicComponentMapperTests, MappingByCode\MappersTests and MappingByCode\ExplicitMappingTests |
BTW, git rebase told me that current branch is up to date... ? |
Probably you did not do PS: I rebased everything |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks to me as good modernization. Now there are some points to tackle/investigate.
@@ -9,21 +13,48 @@ namespace NHibernate.Tuple.Component | |||
[Serializable] | |||
public class DynamicMapComponentTuplizer : AbstractComponentTuplizer | |||
{ | |||
private readonly Mapping.Component component; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Many non underscore prefixed fields in NHibernate seems to have their root in the Java port having kept Java naming convention. Shouldn't we adopt .Net convention for our own variables at least? (I personally tend to add the _
on code I port from Java.)
{ | ||
return typeof(IDictionary); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does not look lightweight for a property getter. Maybe should this be initialized in constructor instead, or implemented with a private Lazy<System.Type>
.
It looks overly complicated when we look at implementation of below DynamicMapInstantiator
: typeof(IDictionary<string, object>).IsAssignableFrom(this.component.Type.ReturnedClass)
. What causes this simpler implementation not to be used here?
Since 5.0 is about modernization too, I think we should default to IDictionary<string, object>
instead of defaulting to IDictionary
. Of course this would require some changes on later files below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What causes this simpler implementation not to be used here?
Because the this.component.Type.ReturnedClass
property calls this property:
nhibernate-core/src/NHibernate/Type/ComponentType.cs
Lines 99 to 103 in 2345bb4
/// <summary></summary> | |
public override System.Type ReturnedClass | |
{ | |
get { return ComponentTuplizer.MappedClass; } | |
} |
So, you cannot fetch the value of this property out of the void.
@@ -48,7 +50,14 @@ public object Instantiate() | |||
|
|||
protected virtual IDictionary GenerateMap() | |||
{ | |||
return new Hashtable(); | |||
if ((this.component != null) && (typeof(IDictionary<string, object>).IsAssignableFrom(this.component.Type.ReturnedClass) == true)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As stated in another review, I do understand neither the added value of comparing to true
, nor the unneeded parenthesis.
if (this.component != null && typeof(IDictionary<string, object>).IsAssignableFrom(this.component.Type.ReturnedClass)
Gets the job done and is more readable, isn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's just his code style, however this needs to be changed to align with our code style.
else | ||
{ | ||
return new Hashtable(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want to default to strong typed dictionary, we should invert the null logic here:
if (this.component == null ||
typeof(IDictionary<string, object>).IsAssignableFrom(this.component.Type.ReturnedClass)
{
return new Dictionary<string, object>();
}
else
{
return new Hashtable();
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We cannot - MapInstantiator
is also used for EntityMode.Map
and that was not migrated to generic dictionary.
@@ -65,7 +74,7 @@ public bool IsInstance(object obj) | |||
} | |||
else | |||
{ | |||
return false; | |||
return obj is IDictionary<string, object>; | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If defaulting to strong typed dictionary, we would have to change the logic here too:
var that = obj as IDictionary<string, object>;
if (that != null)
{
if (entityName == null)
{
return true;
}
string type = (string)that[KEY];
return type == null || isInstanceEntityNames.Contains(type);
}
else
{
return obj is IDictionary;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This piece code here only for EntityMode.Map
instantiator.
[Serializable] | ||
internal class DynamicComponentInstantiator : IInstantiator | ||
{ | ||
private readonly bool _isGeneric; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just for compatibility in case someone has declared a property holding the component as Hashtable
instead of IDictionary
. Otherwise Dictionary<string, object>
when casted to IDictionary
behaves the same as a Hashtable
.
If we do not require/want this level of compatibility we can simplify DynamicComponentInstantiator to this:
[Serializable]
internal class DynamicComponentInstantiator : IInstantiator
{
public object Instantiate(object id) => Instantiate();
public object Instantiate() => new Dictionary<string, object>();
public bool IsInstance(object obj) => obj is IDictionary;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rjperes, @fredericDelaporte your thoughts? I think this can be easily declare as possible breaking change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A possible breaking change, yes it is, but still a grey area one. The documentation clearly states to map it as an IDictionary
.
The
<dynamic-component>
element allows anIDictionary
to be mapped as a component, where the property names refer to keys of the dictionary.
The fact it was a HashTable
is an implementation detail on which the user should not rely:
Users may also plug in their own tuplizers. Perhaps you require that a System.Collections.IDictionary implementation other than System.Collections.Hashtable be used while in the dynamic-map entity-mode;
I think we can accept this possible breaking change in a minor release. (With a small update to above example in documentation.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Users may also plug in their own tuplizers.
<dynamic-component>
does not support custom tuplizers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have removed the _isGeneric check as it did not work in all the cases. It seems that some code depends on ComponentClass
being null for <dynamic-component>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<dynamic-component>
does not support custom tuplizers.
Ok, I did not realize this PR was leaving dynamic entity mode "as-is", using HashTable
, and was just changing the dynamic component. This creates some kind of inconsistency, although that still remains internal implementation details.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this PR's only about dynamic component.
else if (reflectedClass != null) | ||
{ | ||
model.ComponentClass = reflectedClass; | ||
model.IsEmbedded = false; | ||
model.IsDynamic = componentMapping is HbmDynamicComponent; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change to this file can be reverted if we do not care about "invalid" property declarations (see my comment).
Old review with a bunch of false assumptions
…nent - Created additional tests with generic properties for NH-1796, NH-1039, NH-3571 and NH-2664
This is now ready for review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So now we can map a dynamic component as an IDictionary<string, object>
, but we can not yet query a dynamic entity as an IDictionary<string, object>
. An additional PR for this would be preferable, for avoiding what could be seen as a discrepancy.
@@ -140,6 +140,14 @@ protected override void RegisterComponentMapping<TComponent>(Expression<Func<TEn | |||
base.RegisterDynamicComponentMapping(property, mapping); | |||
} | |||
|
|||
protected override void RegisterDynamicComponentMapping<TComponent>(Expression<Func<TEntity, IDictionary<string, object>>> property | |||
, Action<IDynamicComponentMapper<TComponent>> mapping) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Formatting style oddness, NHibernate code normally keeps comma at end of lines rather than at start.
@fredericDelaporte I want to make dynamic component to support |
I’ll merge it myself
|
I just realised that some of the interfaces has changed. I'll make a follow-up PR to remove the breaking changes. |
https://nhibernate.jira.com/browse/NH-3670 (#755)
Created additional tests with generic properties for NH-1796, NH-1039, NH-3571 and NH-2664