I'm working with OData, something I'm not too familiar with. When OData attaches an entity to the Context in the DataServiceContext class, they set the .Identity
property using ODataResourceMetadataBuilder.
ODataResourceMetadataBuilder entityMetadataBuilder = this.GetEntityMetadataBuilderInternal(descriptor);
descriptor.EditLink = entityMetadataBuilder.GetEditLink();
descriptor.Identity = entityMetadataBuilder.GetId();
This goes through a series of OData classes starting with ConventionalODataEntityMetadataBuilder.GetId(), that eventually gets to where the CompositeKey is created in OData's DataServiceUrlKeyDelimiter
internal void AppendKeyExpression(IEdmStructuredValue entity, StringBuilder builder)
{
Debug.Assert(entity != null, "entity != null");
Debug.Assert(builder != null, "builder != null");
IEdmEntityTypeReference edmEntityTypeReference = entity.Type as IEdmEntityTypeReference;
if (edmEntityTypeReference == null || !edmEntityTypeReference.Key().Any())
{
throw Error.Argument(ErrorStrings.Content_EntityWithoutKey, "entity");
}
// Problem occurs here - edmEntityTypeReference.Key() has the keys in the wrong order.
this.AppendKeyExpression(edmEntityTypeReference.Key().ToList(), p => p.Name, p => GetPropertyValue(entity.FindPropertyValue(p.Name), entity.Type), builder);
}
The keys are defined in my class using Key Annotations
public class MyClass
{
[Key, Column(Order = 0)]
public Guid CompositeKeyB { get; set; }
[Key, Column(Order = 1)]
public Guid CompositeKeyA { get; set; }
}
Note that the keys are alphabetically backwards.
When the above code runs to set the .Identity
field, it is giving me the keys in alphabetical order, not in the order I specified in the Data Annotations.
This is a problem because when an entity is retrieved, the Keys are in the correct order so it has a different .Identity
property and is seen as a separate instance than the attached instance. This means that it doesn't update the Attached entity with the new data, and also that a second copy of the entity gets created in the Context.
Is there an easy way to correct this, or am I stuck with writing my own code to get the Keys in the correct order using Reflection? I do not currently see a way of finding the Order value in the IEdmStructuralProperty
property that is returned from IEdmEntityTypeReference.Key()
.
The issue was occurring because in the ClientEdmModel.GetOrCreateEdmTypeInternal
method it is ordering the properties by name before sorting them into key and non-key properties.
// Problem is the .OrderBy in this line of code
foreach (PropertyInfo property in ClientTypeUtil.GetPropertiesOnType(type, /*declaredOnly*/edmBaseType != null).OrderBy(p => p.Name))
{
IEdmProperty edmProperty = this.CreateEdmProperty((EdmStructuredType)entityType, property);
loadedProperties.Add(edmProperty);
if (edmBaseType == null && keyProperties.Any(k => k.DeclaringType == type && k.Name == property.Name))
{
Debug.Assert(edmProperty.PropertyKind == EdmPropertyKind.Structural, "edmProperty.PropertyKind == EdmPropertyKind.Structural");
Debug.Assert(edmProperty.Type.TypeKind() == EdmTypeKind.Primitive || edmProperty.Type.TypeKind() == EdmTypeKind.Enum, "edmProperty.Type.TypeKind() == EdmTypeKind.Primitive || edmProperty.Type.TypeKind() == EdmTypeKind.Enum");
loadedKeyProperties.Add((IEdmStructuralProperty)edmProperty);
}
}
The solution we used was to apply the original sorting to composite key properties afterwards, although a potential solution could also be to remove the .OrderBy(p => p.Name)
from the foreach loop. I'm not sure if this would cause other problems though, so it seems safest to just re-apply the key sort order afterwards for composite keys.
if (loadedKeyProperties.Count > 1)
{
var orderedKeyPropertyNames = keyProperties.Select(k => k.Name).ToList();
loadedKeyProperties = loadedKeyProperties.OrderBy(k => orderedKeyPropertyNames.IndexOf(k.Name)).ToList();
}