diff --git a/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs b/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs index 7447fe9b39c..bbcb507d69c 100644 --- a/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs +++ b/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs @@ -62,9 +62,19 @@ private static PropertyAccessors CreateGeneric(IPropertyBase property Expression.Property(entryParameter, "Entity"), entityClrType); - currentValueExpression = Expression.MakeMemberAccess( - convertedExpression, - propertyBase.GetMemberInfo(forConstruction: false, forSet: false)); + if (propertyBase.IsIndexedProperty()) + { + currentValueExpression = Expression.MakeIndex( + convertedExpression, + propertyBase.PropertyInfo, + new [] { Expression.Constant(propertyBase.Name) }); + } + else + { + currentValueExpression = Expression.MakeMemberAccess( + convertedExpression, + propertyBase.GetMemberInfo(forConstruction: false, forSet: false)); + } if (currentValueExpression.Type != typeof(TProperty)) { diff --git a/test/EFCore.Tests/Metadata/Internal/PropertyAccessorsFactoryTest.cs b/test/EFCore.Tests/Metadata/Internal/PropertyAccessorsFactoryTest.cs new file mode 100644 index 00000000000..b172242febb --- /dev/null +++ b/test/EFCore.Tests/Metadata/Internal/PropertyAccessorsFactoryTest.cs @@ -0,0 +1,88 @@ +// Copyright (c) .NET Foundation. 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 Microsoft.EntityFrameworkCore.ChangeTracking.Internal; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +// ReSharper disable InconsistentNaming +namespace Microsoft.EntityFrameworkCore.Metadata.Internal +{ + public class PropertyAccessorsFactoryTest + { + [Fact] + public void Can_use_PropertyAccessorsFactory_on_indexed_property() + { + var model = new Model(); + var entityType = model.AddEntityType(typeof(IndexedClass)); + var id = entityType.AddProperty("Id", typeof(int)); + var propertyA = entityType.AddIndexedProperty("PropertyA", typeof(string)); + + var contextServices = InMemoryTestHelpers.Instance.CreateContextServices(model); + var stateManager = contextServices.GetRequiredService(); + var factory = contextServices.GetRequiredService(); + + var entity = new IndexedClass(); + var entry = factory.Create(stateManager, entityType, entity); + + var propertyAccessors = new PropertyAccessorsFactory().Create(propertyA); + Assert.Equal("ValueA", ((Func)propertyAccessors.CurrentValueGetter)(entry)); + Assert.Equal("ValueA", ((Func)propertyAccessors.OriginalValueGetter)(entry)); + Assert.Equal("ValueA", ((Func)propertyAccessors.PreStoreGeneratedCurrentValueGetter)(entry)); + Assert.Equal("ValueA", ((Func)propertyAccessors.RelationshipSnapshotGetter)(entry)); + + var valueBuffer = new ValueBuffer(new object[] { 1, "ValueA" }); + Assert.Equal("ValueA", ((Func)propertyAccessors.ValueBufferGetter)(valueBuffer)); + } + + [Fact] + public void Can_use_PropertyAccessorsFactory_on_non_indexed_property() + { + var model = new Model(); + var entityType = model.AddEntityType(typeof(NonIndexedClass)); + var id = entityType.AddProperty("Id", typeof(int)); + var propA = entityType.AddProperty("PropA", typeof(string)); + + var contextServices = InMemoryTestHelpers.Instance.CreateContextServices(model); + var stateManager = contextServices.GetRequiredService(); + var factory = contextServices.GetRequiredService(); + + var entity = new NonIndexedClass(); + var entry = factory.Create(stateManager, entityType, entity); + + var propertyAccessors = new PropertyAccessorsFactory().Create(propA); + Assert.Equal("ValueA", ((Func)propertyAccessors.CurrentValueGetter)(entry)); + Assert.Equal("ValueA", ((Func)propertyAccessors.OriginalValueGetter)(entry)); + Assert.Equal("ValueA", ((Func)propertyAccessors.PreStoreGeneratedCurrentValueGetter)(entry)); + Assert.Equal("ValueA", ((Func)propertyAccessors.RelationshipSnapshotGetter)(entry)); + + var valueBuffer = new ValueBuffer(new object[] { 1, "ValueA" }); + Assert.Equal("ValueA", ((Func)propertyAccessors.ValueBufferGetter)(valueBuffer)); + } + + private class IndexedClass + { + private Dictionary _internalValues = new Dictionary() + { + { "PropertyA", "ValueA" } + }; + + internal int Id { get; set; } + + public object this[string name] + { + get => _internalValues[name]; + } + } + + private class NonIndexedClass + { + internal int Id { get; set; } + public string PropA { get; set; } = "ValueA"; + } + } +}