Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EF SqlQuery tries to map object property, even though it's attributed [NotMapped]

I have a stored procedure that I execute with context.Database.SqlQuery<MyObject>("MyProc")

MyObject has a readonly property:

[NotMapped]
public bool IsSomething { get { return this.otherproperty == "something"; } }

So I get the error:

System.IndexOutOfRangeException: IsSomething at System.Data.ProviderBase.FieldNameLookup.GetOrdinal etc

Which is because MyProc doesn't have IsSomething in the result columns (I'm 100% sure that's probably the reason).

Shouldn't it just ignore it since it's [NotMapped]? Do I need to set something else for SqlQuery?

To make things even more weird, I only see it on production, from the logs of Stackify, and the page seems to load properly without any errors in the browser.

like image 764
Alx Avatar asked Aug 16 '17 13:08

Alx


2 Answers

According to documentation

Database.SqlQuery<TElement> Method (String, Object[])

Creates a raw SQL query that will return elements of the given generic type. The type can be any type that has properties that match the names of the columns returned from the query, or can be a simple primitive type. The type does not have to be an entity type. The results of this query are never tracked by the context even if the type of object returned is an entity type.....

emphasis mine

It is looking in the reader returned from query for the IsSomething as indicated by System.Data.ProviderBase.FieldNameLookup.GetOrdinal in the error message.

Because that column does not exist you will get the IndexOutOfRangeException.

Shouldn't it just ignore it since it's [NotMapped] ?

No. It ignores any attributes present on the object, including [NotMapped]. Refer to quote referenced above

Do I need to set something else for SqlQuery ?

I would suggest creating another object with only properties that match/map to the expected result of the procedure and using that as the generic argument of the SqlQuery call.

like image 114
Nkosi Avatar answered Nov 15 '22 06:11

Nkosi


To make things even more weird, I only see it on production, from the logs of Stackify, and the page seems to load properly without any errors

I'm not familiar with Stackify logs, but based on my knowledge of EF6 I would speculate and say this is false positive log.

The documentation is not quite clear in that regard. First, entity and non entity SqlQuery result types are processed by different code branches. While it's true that both translations use CLR property names as column names (i.e. column name mappings for entity types are not taken into account), the ignored (NotMapped) properties of the entity types are indeed excluded from the result as expected.

Also the IndexOutOfRangeException in question is caught by EF code and translated to a different exception (EntityCommandExecutionException), which can be seen from the source code of the relevant methods GetMemberOrdinalFromReader:

// <summary>
// Given a store datareader and a member of an edmType, find the column ordinal
// in the datareader with the name of the member.
// </summary>
private static int GetMemberOrdinalFromReader(
    DbDataReader storeDataReader, EdmMember member, EdmType currentType,
    Dictionary<string, FunctionImportReturnTypeStructuralTypeColumnRenameMapping> renameList)
{
    int result;
    var memberName = GetRenameForMember(member, currentType, renameList);

    if (!TryGetColumnOrdinalFromReader(storeDataReader, memberName, out result))
    {
        throw new EntityCommandExecutionException(
            Strings.ADP_InvalidDataReaderMissingColumnForType(
                currentType.FullName, member.Name));
    }
    return result;
}

and TryGetColumnOrdinalFromReader:

// <summary>
// Given a store datareader and a column name, try to find the column ordinal
// in the datareader with the name of the column.
// </summary>
// <returns> true if found, false otherwise. </returns>
private static bool TryGetColumnOrdinalFromReader(DbDataReader storeDataReader, string columnName, out int ordinal)
{
    if (0 == storeDataReader.FieldCount)
    {
        // If there are no fields, there can't be a match (this check avoids
        // an InvalidOperationException on the call to GetOrdinal)
        ordinal = default(int);
        return false;
    }

    // Wrap ordinal lookup for the member so that we can throw a nice exception.
    try
    {
        ordinal = storeDataReader.GetOrdinal(columnName);
        return true;
    }
    catch (IndexOutOfRangeException)
    {
        // No column matching the column name found
        ordinal = default(int);
        return false;
    }
}

Based on the above and the lack of repro with real exception, I would say you can safely ignore that log.

like image 31
Ivan Stoev Avatar answered Nov 15 '22 07:11

Ivan Stoev