ra0o0f / arangoclient.net

ArangoDB .NET Client with LINQ support
Apache License 2.0
99 stars 37 forks source link

Annotating a property of type Guid as DocumentProperty.Key throws InvalidCastException #128

Open TommyEmmanuel opened 5 years ago

TommyEmmanuel commented 5 years ago

As the title suggests, I have had all my class Id properties decorated with [DocumentProperty(Identifier = IdentifierType.Key)] All Id properties are of type System.Guid and thus far had no issues with it. As a matter of fact, i used a generic base type to get any type on Id property on all my entities.

        public abstract class BaseEntity<TId> : IEntity<TId>
        {
            [DocumentProperty(Identifier = IdentifierType.Key)]
            [DataMember(Name = "id")]
            public TId Id { get; set; }
        }

and it worked wonderfully. I have been using this originally in the prior version v0.7.6. Since the update, I've been getting InvalidCastException with the message:

Unable to cast object of type 'System.String' to type 'System.Guid'.

The exception happens somewhere within a lambda expression and the closest to the line of code where i have been able to investigate is: ArangoDB.Client.Property.DocumentIdentifierModifier in the line 55 This line tries to generically create a SetValue (there is even a stackoverflow link attached)

I have also tried cloning the project and updating all the nuget references thinking it would help, but no progress there.

This breaking change happened with the updated version v0.7.70.

dkincer87 commented 5 years ago

I am also having the same issue with inserting a vertex.

[DocumentProperty(Identifier = IdentifierType.Key)]
public Guid Id { get; set; }

When hitting the arango .net method

IDocumentIdentifierResult result = await graph.InsertVertexAsync<Tnode>(node);

It tries to return the interface which is expecting a string instead of a Guid. Is there a way to patch this to allow the interface Key to be the type we assign the DocumentProperty?

Alan5142 commented 5 years ago

This can be solved using TypeDescriptor.GetConverter as shown here:

var methods = FindIdentifierMethodFor(document.GetType());
methods.SetKey(document, TypeDescriptor.GetConverter(PROPERTY_TYPE).ConvertFrom(identifiers.Key));

PROPERTY_TYPE is the type of the property that will obtain the value

In my local branch I edited the following file to be able to use Guid as an Id type: DocumentIdentifierModifier.cs

public class IdentifierMethod
{
    public IdentifierMethod()
    {
        this.SetKey = this.SetRevision = this.SetHandle = (x, y) => { };
    }
    // this was added
    public Type KeyType { get; set; }

    public Action<object, object> SetKey { get; set; }
    public Action<object, object> SetHandle { get; set; }
    public Action<object, object> SetRevision { get; set; }
    public Action<object, object> SetFrom { get; set; }
    public Action<object, object> SetTo { get; set; }
}
public IdentifierMethod FindIdentifierMethodFor(Type type)
{
    IdentifierMethod identifierMethod = null;
    if (!methods.TryGetValue(type, out identifierMethod))
    {
        identifierMethod = new IdentifierMethod();
        var typeMemberInfos = ReflectionUtils.GetFieldsAndProperties_PublicInstance(type);
        foreach (var m in typeMemberInfos)
        {
            string resolvedName = setting.Collection.ResolvePropertyName(type, m.Name);

            switch (resolvedName)
            {
                case "_key":
                    // following line was added
                    identifierMethod.KeyType = ReflectionUtils.GetProperty(type, m.Name).PropertyType;
                    identifierMethod.SetKey = BuildSetAccessor(type, m.Name);
public void Modify(object document, IDocumentIdentifierResult identifiers)
{
    if (identifiers.Id != null && identifiers.Key != null && identifiers.Rev != null)
    {
        var methods = FindIdentifierMethodFor(document.GetType());
        // Use type converter
        methods.SetKey(document, TypeDescriptor.GetConverter(methods.KeyType).ConvertFrom(identifiers.Key));
        methods.SetHandle(document, identifiers.Id);
        methods.SetRevision(document, identifiers.Rev);
    }
}

public void Modify(object document, IDocumentIdentifierResult identifiers, string from, string to)
{
    if (identifiers.Id != null && identifiers.Key != null && identifiers.Rev != null)
    {
        var methods = FindIdentifierMethodFor(document.GetType());
        // // Use type converter
        methods.SetKey(document, TypeDescriptor.GetConverter(methods.KeyType).ConvertFrom(identifiers.Key));
        methods.SetHandle(document, identifiers.Id);
        methods.SetRevision(document, identifiers.Rev);
        methods.SetFrom(document, from);
        methods.SetTo(document, to);
    }
}

If you want to use another type, be sure that the destination type has a type converter from string 😄 I will make a PR after the PR that I made a few hours ago is accepted