-
-
Notifications
You must be signed in to change notification settings - Fork 182
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
[BUG] Incorrect InnerJoin and RightJoin implementaion #318
Comments
I face the same issue. What documentation are we supposed to trust here? In the wiki https://github.com/reactivemarbles/DynamicData/wiki/Sql-style-joins it states that "These operators produces one-to-one data mappings" while in
it states that it takes ALL right values |
@RolandPheasant so I was working on this, and the following seems to work correctly for me. Feel free to use this in the library (and please take a look to see if you spot any immediate red flags in the code). public class CorrectRightJoin<TLeft, TLeftKey, TRight, TRightKey, TDestination>
where TLeftKey : notnull
where TRightKey : notnull
{
private readonly IObservable<IChangeSet<TLeft, TLeftKey>> _left;
private readonly Func<TRightKey, Optional<TLeft>, TRight, TDestination> _resultSelector;
private readonly IObservable<IChangeSet<TRight, TRightKey>> _right;
private readonly Func<TRight, TLeftKey> _rightKeySelector;
public CorrectRightJoin(IObservable<IChangeSet<TLeft, TLeftKey>> left, IObservable<IChangeSet<TRight, TRightKey>> right,
Func<TRight, TLeftKey> rightKeySelector, Func<TRightKey, Optional<TLeft>, TRight, TDestination> resultSelector)
{
_left = left ?? throw new ArgumentNullException(nameof(left));
_right = right ?? throw new ArgumentNullException(nameof(right));
_rightKeySelector = rightKeySelector ?? throw new ArgumentNullException(nameof(rightKeySelector));
_resultSelector = resultSelector ?? throw new ArgumentNullException(nameof(resultSelector));
}
public IObservable<IChangeSet<TDestination, TRightKey>> Run()
{
return Observable.Create<IChangeSet<TDestination, TRightKey>>(
observer =>
{
var locker = new object();
// create local backing stores
var leftCache = _left.Synchronize(locker).AsObservableCache(false);
var rightCache = _right.Synchronize(locker).AsObservableCache(false);
var rightGrouped = _right.Synchronize(locker).GroupWithImmutableState(_rightKeySelector).AsObservableCache(false);
// joined is the final cache
var joinedCache = new LockFreeObservableCache<TDestination, TRightKey>();
var rightLoader = rightCache.Connect().Subscribe(
changes =>
{
joinedCache.Edit(
innerCache =>
{
foreach (var change in changes.ToConcreteType())
{
var leftKey = _rightKeySelector(change.Current);
switch (change.Reason)
{
case ChangeReason.Add:
case ChangeReason.Update:
// Update with right (and right if it is presents)
var right = change.Current;
var left = leftCache.Lookup(leftKey);
innerCache.AddOrUpdate(_resultSelector(change.Key, left, right), change.Key);
break;
case ChangeReason.Remove:
// remove from result because a right value is expected
innerCache.Remove(change.Key);
break;
case ChangeReason.Refresh:
// propagate upstream
innerCache.Refresh(change.Key);
break;
}
}
});
});
var leftLoader = leftCache.Connect().Subscribe(
changes =>
{
joinedCache.Edit(
innerCache =>
{
foreach (var change in changes.ToConcreteType())
{
TLeft left = change.Current;
var right = rightGrouped.Lookup(change.Key);
if (right.HasValue)
switch (change.Reason)
{
case ChangeReason.Add:
case ChangeReason.Update:
foreach (var (key, value) in right.Value.KeyValues)
innerCache.AddOrUpdate(_resultSelector(key, left, value), key);
break;
case ChangeReason.Remove:
foreach (var (key, value) in right.Value.KeyValues)
innerCache.AddOrUpdate(_resultSelector(key, Optional<TLeft>.None, value), key);
break;
case ChangeReason.Refresh:
foreach (var key in right.Value.Keys)
innerCache.Refresh(key);
break;
}
}
});
});
return new CompositeDisposable(joinedCache.Connect().NotEmpty().SubscribeSafe(observer), leftCache, rightCache, rightLoader, joinedCache,
leftLoader);
});
}
} |
There's no obvious red flags. The key for me is with these changes do the existing tests pass, and also are there additional test cases which should pass? |
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
This is just an example of one-to-many, where the a
subrecord
could be referenced/shared by many parentrecords
.Inner join might make sense not to return duplicates, but cardinality of right join should be that of the set on the right.
The result is invalid:
Expected result is rightJoinResult count of 3 or
Here is the corresponding SQL:
and the result:
PS: It looks like
var rightCache = right.Synchronize(locker).ChangeKey(_rightKeySelector).AsObservableCache(false);
assumes that subrecords could not be shared by parent records ?? as ChangeKey eliminates all but one parents that share subrecords (unique key constraint???).
replacing RightJoin with this Transform produces expected result but I am unsure about concurrency issues
PS2: This is a follow-up to a closed question/issue about many-many relationships implementation #239
The correct solution there is to use a
RightJoin
instead of recommended InnerJoinThe text was updated successfully, but these errors were encountered: