Azure / azure-cosmos-dotnet-v2

Contains samples and utilities relating to the Azure Cosmos DB .NET SDK
MIT License
578 stars 837 forks source link

Linq Where IN enum handling problem #418

Open dmcweeney opened 6 years ago

dmcweeney commented 6 years ago

Hi,

Noticed the following problem last night.

I have a property on the document defined as follows where ChangeType is an enum:

[JsonProperty( PropertyName = "changeType", Required = Required.Always, Order = 0 )]
[JsonConverter( typeof( StringEnumConverter ) )]
public ChangeType ChangeType
{
    get { return this.m_changeType ; }
}

The following Where equals clause correctly converts to the enum string value:

.Where( doc => doc.Changes[0].ChangeType == enumValue )
AND (root["changes"][0]["changeType"] = "Status")

The following Where contains clause incorrectly converts to the enum decimal values, but should be converting to a list of string values:

.Where( doc => enumValues.Contains( doc.Changes[0].ChangeType ) 
AND (root["changes"][0]["changeType"] IN (1, 2))

Using Microsoft.Azure.DocumentDB.Core 1.5.1

Thanks

Donal

bchong95 commented 6 years ago

Can you provide us both linq queries in full and what sql queries they translate to (you can get this by running .ToString() on the documentQuery).

Can you also see if this issue repros in the latest SDK?

Thank you.

bchong95 commented 6 years ago

@khdang Could this have something to do with Newtonsoft Json Serializers?

dmcweeney commented 6 years ago

Hi @bchong95 Apologies but I changed the code back to persist the enum value as int so I cannot reproduce. The linq query in full would have been like:

IQueryable<TestDoc> testQuery = base.DbContext.DocumentDbRepository.CreateDocumentQuery<TestDoc>()
    .Where( doc => doc.DocumentType == "TestDoc" )
    .Where( doc => doc.IdField == "idFieldValue" )

    if ( changeTypes.Count == 1 )
    {
        testQuery = testQuery.Where( doc => doc.Changes[0].ChangeType == changeTypes[0] );
    }
    else
    {
        testQuery = testQuery.Where( doc => changeTypes.Contains( doc.Changes[0].ChangeType ) );
    }

    testQuery = testQuery.OrderByDescending( doc => doc.CreatedOn ) ;

Hope this helps...

I only just saw that 1.7.1 is released - but wont be able to test on this as code is reverted...

Thanks

Donal

tkleijkers commented 5 years ago

I have exactly the same problem, and I am also using the Json serializers:

        [EnumDataType(typeof(ReportStatus))]
        [JsonConverter(typeof(StringEnumConverter))]
        public ReportStatus ReportStatus { get; set; }

Expression: searchExpression = (x => x.Type == "Report" && (x.Title.StartsWith(search) || x.Email.StartsWith(search)) && onlyStatus.Contains(x.ReportStatus));

Where onlyStatus = IEnumerable< ReportStatus >

ToString:

"x => (((x.Type == \"Report\") AndAlso (x.Title.StartsWith(value(Proj.API.Services.ReportDbService+<>c__DisplayClass3_0).search) OrElse x.Email.StartsWith(value(Proj.API.Services.ReportDbService+<>c__DisplayClass3_0).search))) AndAlso value(Proj.API.Services.ReportDbService+<>c__DisplayClass3_0).onlyStatus.Contains(x.ReportStatus))"
dmcweeney commented 5 years ago

I've stayed with persisting enum values as int. Other option to consider is building the SQL queries by hand, and not using Linq.

silverholm commented 5 years ago

I had the same issue as well, and I also ended up persisting the enum values as int. I believe this is related to expressions for IQueryables. As soon as you compile the query (IEnumerable) it will work.

public int GetCount(Expression<Func<TEntity, bool>> predicate, Func<TEntity, bool> enumValues) { var queryExample = _client.CreateDocumentQuery<TEntity>(Collection.SelfLink, new FeedOptions { EnableCrossPartitionQuery = true }).Where(predicate).Where(enumValues).Count(); return queryExample }

captainrdubb commented 5 years ago

@bchong95 Same here Microsoft.Azure.DocumentDB.Core v2.1.3 Newtonsoft v9.0.1

public IQueryable<Bidness> AppendTo(IQueryable<Bidness> queryable)
        {
            return queryable.Where(
                b => b.Time == Time &&
                b.StartDate <= Date &&
                b.EndDate >= Date &&
                b.ByDay.Contains(DayOfWeek));
        }

{{"query":"SELECT * FROM root WHERE ((((root[\"time\"] = \"14:52:00+00:00\") AND (root[\"startDate\"] <= \"2018-10-20T00:00:00\")) AND (root[\"endDate\"] >= \"2018-10-20T00:00:00\")) AND ARRAY_CONTAINS(root[\"byDay\"], 6)) "}}

maranmaran commented 1 year ago

I'm experiencing the same issue on v3

<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.32.0" />
var queryable = Container.GetItemLinqQueryable<TDocument>().AsQueryable();

if (filter != null)
    queryable = queryable.Where(filter);

using var iterator = queryable.ToFeedIterator();

// ...get results
Expression<Func<CustomEntity, bool>> query = item => item.Status == Status.Queued || item.Status == Status.InProgress;
  [JsonInclude]
  [JsonConverter(typeof(JsonStringEnumConverter))]
  public Status Status { get; private set; }