Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JsonContains and sub-properties #1616

Open
DashNY opened this issue Dec 18, 2020 · 12 comments
Open

JsonContains and sub-properties #1616

DashNY opened this issue Dec 18, 2020 · 12 comments
Labels
enhancement New feature or request
Milestone

Comments

@DashNY
Copy link

DashNY commented Dec 18, 2020

Is there a support for querying by a sub-property of an object within a collection?
For example, I have the following classes defined

public class MyEntity
{
    [Column("Info", TypeName = "jsonb")]
    public Info Info { get; set; }
}

public class Info
{
    public List<Tag> Tags { get; set; }
}

public class Tag
{
   public int TagId { get; set; }
   public int OtherData { get; set; }
}

How do I find all the MyEntity rows that have at least one Tag with TagId = 10 ?
Here's what I've tried so far:

// didn't work
var result = db.MyEntities.Where(m => EF.Functions.JsonContains(m.Info.Tags.Select(t => t.TagId), "10") ).ToList();
@roji
Copy link
Member

roji commented Dec 18, 2020

@roji roji added this to the Backlog milestone Dec 18, 2020
@roji roji added the enhancement New feature or request label Dec 18, 2020
@roji
Copy link
Member

roji commented Dec 18, 2020

This isn't supported at the moment. It would probably be doable via jsonpath (see #1045).

@don-flamingo
Copy link

Is exists any workaround for it?

I Tried something like it:

if (request.Tags != null && request.Tags.Any())
{
      queryable = queryable.Where(x => EF.Functions.JsonExistAny(x.Tags, request.Tags));
}

But still I only have got an exception:

The LINQ expression 'DbSet<DeviceLibraryItem>()
    .Where(d => d.TenantId == __request_TenantId_0)
    .Where(d => d.FolderId == __request_FolderId_1)
    .Where(d => __Functions_2
        .JsonExistAny(
            json: d.Tags, 
            keys: __request_Tags_3))' could not be translated. Additional information: Translation of member 'Tags' on entity type 'DeviceLibraryItem' failed. This commonly occurs when the specified member is unmapped. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

@roji
Copy link
Member

roji commented Mar 25, 2021

I don't think there's a workaround at this point - though you can always use raw SQL.

Note to self: JsonExistsAny translates to the ?| operator, which accepts an array on the right-hand side. #1691 will most probably introduce an expression for ARRAY[], at which point we can embed a subquery inside that.

@don-flamingo
Copy link

raw SQL does not help me in that case...
I think about custom IMethodCallTranslator for translate it to SQL part:

SELECT c."Id", c."Name", c."Tags"
	FROM public."DevicesLibrary" AS c
	WHERE (c."Tags" @> '[{"Value": "yellow"}]' OR  
		   c."Tags" @> '[{"Value": "dark-choco"}]')

@roji
Copy link
Member

roji commented Apr 6, 2021

If this SQL is what you're looking for (expand JsonExistAny to a bunch of ORs), it should be pretty simple to write a method that does this yourself, running a visitor over the expression tree and performing the necessary replacing. However, it's not something that I think belongs in the provider - expanding to ORs means that different SQL is generated based on different values, which is problematic for query/plan caching.

@Nordes
Copy link

Nordes commented Nov 16, 2021

It would be nice to have something like ContainsAny which would translate to c."Tags"->"Value" @> ANY('{"yellow","dark-choco"}'). I was not able to make this using the existing functions since the ANY can't appear without using a database item.

@roji
Copy link
Member

roji commented Nov 16, 2021

Yeah, that should indeed be possible. For completeness, the actual query would be:

SELECT '{"a":1, "b":2}'::jsonb @> ANY(ARRAY['{"b":1}'::jsonb, '{"a":1}'::jsonb]);

@LINDFJ25477-pki
Copy link

Wouldn't this work in PostgreSQL syntax?

SELECT c."Id", c."Name", c."Tags"
FROM public."DevicesLibrary" AS c
CROSS JOIN LATERAL jsonb_array_elements(c."Tags") tags(tag)
WHERE (tag->>"Value")::text IN ("yellow", "dark-choco")

@roji
Copy link
Member

roji commented Jan 25, 2022

@LINDFJ25477-pki any particular advantage to that over the terser alternative above?

@grcd
Copy link

grcd commented Apr 19, 2022

Is there a support for querying by a sub-property of an object within a collection? For example, I have the following classes defined

public class MyEntity
{
    [Column("Info", TypeName = "jsonb")]
    public Info Info { get; set; }
}

public class Info
{
    public List<Tag> Tags { get; set; }
}

public class Tag
{
   public int TagId { get; set; }
   public int OtherData { get; set; }
}

How do I find all the MyEntity rows that have at least one Tag with TagId = 10 ? Here's what I've tried so far:

// didn't work
var result = db.MyEntities.Where(m => EF.Functions.JsonContains(m.Info.Tags.Select(t => t.TagId), "10") ).ToList();

What if:

var result = db.MyEntities.Where(m => EF.Functions.JsonContains(m.Info, @"{""Tags"": {""TagId"": ""10""}}")).ToList();

?

@roji
Copy link
Member

roji commented Apr 20, 2022

@grcd that checks whether the entire Tags sub-document exists as provided, so it checks whether there's only one tag with ID 10 (any additional tag will cause this to return false).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

6 participants