Skip to content
Merged
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
41 changes: 16 additions & 25 deletions src/Microsoft.Data.Entity/ChangeTracking/NavigationFixer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.Data.Entity.Metadata;
Expand All @@ -13,8 +12,7 @@ namespace Microsoft.Data.Entity.ChangeTracking
public class NavigationFixer : IEntityStateListener
{
private readonly StateManager _stateManager;
private readonly ClrCollectionAccessorSource _collectionAccessorSource;
private readonly ClrPropertySetterSource _setterSource;
private readonly NavigationAccessorSource _accessorSource;

/// <summary>
/// This constructor is intended only for use when creating test doubles that will override members
Expand All @@ -27,16 +25,13 @@ protected NavigationFixer()

public NavigationFixer(
[NotNull] StateManager stateManager,
[NotNull] ClrCollectionAccessorSource collectionAccessorSource,
[NotNull] ClrPropertySetterSource setterSource)
[NotNull] NavigationAccessorSource accessorSource)
{
Check.NotNull(stateManager, "stateManager");
Check.NotNull(collectionAccessorSource, "collectionAccessorSource");
Check.NotNull(setterSource, "setterSource");
Check.NotNull(accessorSource, "accessorSource");

_stateManager = stateManager;
_collectionAccessorSource = collectionAccessorSource;
_setterSource = setterSource;
_accessorSource = accessorSource;
}

public virtual void StateChanging(StateEntry entry, EntityState newState)
Expand Down Expand Up @@ -81,37 +76,33 @@ private void DoFixup(IForeignKey foreignKey, StateEntry principalEntry, StateEnt
{
foreach (var navigation in _stateManager.Model.GetNavigations(foreignKey))
{
var accessor = _accessorSource.GetAccessor(navigation);

if (navigation.PointsToPrincipal)
{
var accessor = _setterSource.GetAccessor(navigation);

foreach (var dependent in dependentEntries)
{
accessor.SetClrValue(dependent.Entity, principalEntry.Entity);
accessor.Setter.SetClrValue(dependent.Entity, principalEntry.Entity);
}
}
else
{
// TODO: Avoid doing this lookup every time--should be cached
if (navigation.EntityType.Type.GetAnyProperty(navigation.Name).PropertyType.TryGetElementType(typeof(IEnumerable<>)) == null)
{
var accessor = _setterSource.GetAccessor(navigation);

// TODO: Decide how to handle case where multiple values match non-collection nav prop
accessor.SetClrValue(principalEntry.Entity, dependentEntries.Single().Entity);
}
else
var collectionAccessor = accessor as CollectionNavigationAccessor;
if (collectionAccessor != null)
{
var accessor = _collectionAccessorSource.GetAccessor(navigation);

foreach (var dependent in dependentEntries)
{
if (!accessor.Contains(principalEntry.Entity, dependent.Entity))
if (!collectionAccessor.Collection.Contains(principalEntry.Entity, dependent.Entity))
{
accessor.Add(principalEntry.Entity, dependent.Entity);
collectionAccessor.Collection.Add(principalEntry.Entity, dependent.Entity);
}
}
}
else
{
// TODO: Decide how to handle case where multiple values match non-collection nav prop
accessor.Setter.SetClrValue(principalEntry.Entity, dependentEntries.Single().Entity);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public static EntityServicesBuilder AddEntityFramework([NotNull] this IServiceCo
.AddSingleton<ClrPropertyGetterSource>()
.AddSingleton<ClrPropertySetterSource>()
.AddSingleton<ClrCollectionAccessorSource>()
.AddSingleton<NavigationAccessorSource>()
.AddSingleton<EntityMaterializerSource>()
.AddSingleton<CompositeEntityKeyFactory>()
.AddSingleton<MemberMapper>()
Expand Down
30 changes: 30 additions & 0 deletions src/Microsoft.Data.Entity/Metadata/CollectionNavigationAccessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using JetBrains.Annotations;
using Microsoft.Data.Entity.Utilities;

namespace Microsoft.Data.Entity.Metadata
{
public class CollectionNavigationAccessor : NavigationAccessor
{
private readonly ThreadSafeLazyRef<IClrCollectionAccessor> _collectionAccessor;

public CollectionNavigationAccessor(
[NotNull] Func<IClrPropertyGetter> getter,
[NotNull] Func<IClrPropertySetter> setter,
[NotNull] Func<IClrCollectionAccessor> collectionAccessor)
: base(getter, setter)
{
Check.NotNull(collectionAccessor, "collectionAccessor");

_collectionAccessor = new ThreadSafeLazyRef<IClrCollectionAccessor>(collectionAccessor);
}

public virtual IClrCollectionAccessor Collection
{
get { return _collectionAccessor.Value; }
}
}
}
36 changes: 36 additions & 0 deletions src/Microsoft.Data.Entity/Metadata/NavigationAccessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using JetBrains.Annotations;
using Microsoft.Data.Entity.Utilities;

namespace Microsoft.Data.Entity.Metadata
{
public class NavigationAccessor
{
private readonly ThreadSafeLazyRef<IClrPropertyGetter> _getter;
private readonly ThreadSafeLazyRef<IClrPropertySetter> _setter;

public NavigationAccessor(
[NotNull] Func<IClrPropertyGetter> getter,
[NotNull] Func<IClrPropertySetter> setter)
{
Check.NotNull(getter, "getter");
Check.NotNull(setter, "setter");

_getter = new ThreadSafeLazyRef<IClrPropertyGetter>(getter);
_setter = new ThreadSafeLazyRef<IClrPropertySetter>(setter);
}

public virtual IClrPropertyGetter Getter
{
get { return _getter.Value; }
}

public virtual IClrPropertySetter Setter
{
get { return _setter.Value; }
}
}
}
64 changes: 64 additions & 0 deletions src/Microsoft.Data.Entity/Metadata/NavigationAccessorSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Microsoft.Data.Entity.Utilities;

namespace Microsoft.Data.Entity.Metadata
{
public class NavigationAccessorSource
{
private readonly ThreadSafeDictionaryCache<Tuple<Type, string>, NavigationAccessor> _cache
= new ThreadSafeDictionaryCache<Tuple<Type, string>, NavigationAccessor>();

private readonly ClrCollectionAccessorSource _collectionAccessorSource;
private readonly ClrPropertySetterSource _setterSource;
private readonly ClrPropertyGetterSource _getterSource;

public NavigationAccessorSource(
[NotNull] ClrPropertyGetterSource getterSource,
[NotNull] ClrPropertySetterSource setterSource,
[NotNull] ClrCollectionAccessorSource collectionAccessorSource)
{
Check.NotNull(getterSource, "getterSource");
Check.NotNull(setterSource, "setterSource");
Check.NotNull(collectionAccessorSource, "collectionAccessorSource");

_getterSource = getterSource;
_setterSource = setterSource;
_collectionAccessorSource = collectionAccessorSource;
}

public virtual NavigationAccessor GetAccessor([NotNull] INavigation navigation)
{
Check.NotNull(navigation, "navigation");

return _cache.GetOrAdd(
Tuple.Create(navigation.EntityType.Type, navigation.Name),
k => Create(navigation));
}

private NavigationAccessor Create(INavigation navigation)
{
var elementType = navigation.EntityType.Type.GetAnyProperty(navigation.Name).PropertyType.TryGetElementType(typeof(IEnumerable<>));

var targetType = navigation.PointsToPrincipal
? navigation.ForeignKey.ReferencedEntityType
: navigation.ForeignKey.EntityType;

// TODO: Consider allowing an annotation to force treating a reference to an Entity which is an IEnumerable of
// itself as a reference instead of a collection. Currently it will be considered a collection nav prop, which it isn't.

return elementType != null && elementType == targetType.Type
? new CollectionNavigationAccessor(
() => _getterSource.GetAccessor(navigation),
() => _setterSource.GetAccessor(navigation),
() => _collectionAccessorSource.GetAccessor(navigation))
: new NavigationAccessor(
() => _getterSource.GetAccessor(navigation),
() => _setterSource.GetAccessor(navigation));
}
}
}
3 changes: 3 additions & 0 deletions src/Microsoft.Data.Entity/Microsoft.Data.Entity.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@
<Compile Include="Identity\ValueGeneratorSelector.cs" />
<Compile Include="Infrastructure\IDbContextOptionsExtensions.cs" />
<Compile Include="INotifyPropertyChanging.cs" />
<Compile Include="Metadata\CollectionNavigationAccessor.cs" />
<Compile Include="Metadata\NavigationAccessor.cs" />
<Compile Include="Metadata\NavigationAccessorSource.cs" />
<Compile Include="Query\AsyncQueryCompilationContext.cs" />
<Compile Include="Query\AsyncResultOperatorHandler.cs" />
<Compile Include="Query\IResultOperatorHandler.cs" />
Expand Down
Loading