question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Calling raw SQL with return type IQueryable<T>

See original GitHub issue

Good afternoon, and I am using Microsoft.EntityFrameworkCore 5.0.11 and linq2db.EntityFrameworkCore 5.0.8, I am trying your library for the first time and I have a few questions. I have:

public enum EdgeType
{
    ...
}

public class Edge
{
    public Guid FromId { get; set; }
    public Node From { get; set; }
    public Guid ToId { get; set; }
    public Node To { get; set; }
    public EdgeType Type { get; set; }
}

public abstract class Node
{
    public string OwnerType { get; set; }
    public Guid OwnerId { get; set; }
    public ICollection<Edge> Edges { get; set; }
}

public class GraphPath
{
    public Guid[] Path { get; set; }
    public Guid FromId { get; set; }
    public Guid ToId { get; set; }
    public EdgeType Type { get; set; }
    public int Depth { get; set; }
}

public class DataContext : DbContext
{
    ...
    public DbSet<Node> Nodes { get; set; }
    public DbSet<Edge> Edges { get; set; }
    public DbSet<GraphPath> GraphPath { get; set; }
    public IQueryable<GraphPath> SearchGraph(Node root, EdgeType[] types, int depth, int limit)
    {
        ...
    }
    ...
}

SQL function with signature

create or replace function search_graph(IN "root_id" uuid,
                                        IN "types" edge_type[] default array[]::edge_type[],
                                        IN "depth" int default 99999,
                                        IN "limit" int8 default 2000000000,
                                        out "Path" uuid[],
                                        out "FromId" uuid,
                                        out "ToId" uuid,
                                        out "Type" edge_type,
                                        out "Depth" int)
    returns setof record as
$$
declare
    sql text;
begin
    sql := format($_$ WITH RECURSIVE search( "FromId", "ToId", "Type", "Depth", "Path" ) AS
    (select ...)
$_$, "root_id", "types", "limit", "depth");
    return query execute sql;
end;
$$ language plpgsql strict;

which returns a table with multiple rows of data.

I want to get: I need to implement requests like

{
    ...
    var q = from node in db.Nodes
            let grath = db.SearchGraph(node, types, 10, 10)
            where grath.Any()//or other conditions with ".Where"
            select new { node, grath = grath.ToList() };

    var result = q.ToLinqToDB().ToList();
    ...
}

How I tried to implement this:

i had several attempts to implement this

version 1, use FromSqlRaw EF Core

public IQueryable<GraphPath> SearchGraph1(Node node, EdgeType[] types, int depth, int limit)
{
    var nameTranslator = NpgsqlConnection.GlobalTypeMapper.DefaultNameTranslator;
    var typesStr = string.Join("','", types.Select(t => nameTranslator.TranslateMemberName(t.ToString())));

    var sql = $"select * from search_graph({node.OwnerId}, array['{typesStr}']::edge_type[], {depth}, {limit})";
    return GraphPath.FromSqlRaw(sql).ToLinqToDB();
}

version 2, use FromSql linq2db

public IQueryable<GraphPath> SearchGraph2(Node root, EdgeType[] types, int depth = 99999, int limit = 2000000000)
{
    var nameTranslator = NpgsqlConnection.GlobalTypeMapper.DefaultNameTranslator;
    var typesStr = string.Join("','", types.Select(t => nameTranslator.TranslateMemberName(t.ToString())));

    var sql = $"select * from search_graph('{root.OwnerId}', array['{typesStr}']::edge_type[], {depth}, {limit})";
    return this.CreateLinqToDbContext().FromSql<GraphPath>(sql);
}

version 3, use Sql.Extension

[Sql.Extension("select * from search_graph('{root}', array['{types}']::edge_type[], {depth}, {limit})", ServerSideOnly = true)]
public static IQueryable<GraphPath> SearchGraph3(Guid root, string types, int depth, int limit)
{
    throw new LinqException($"'{nameof(SearchGraph)}' is server-side method.");
}

version 4, use recursive CTE

public IQueryable<GraphPath> SearchGraph4(Node node, EdgeType[] types, int depth = 99999, int limit = 2000000000)
{
    var graphSte = this.CreateLinqToDbContext().GetCte<GraphPath>(search =>
        (
            (
                from edge in Edges
                where edge.FromId == node.OwnerId
                        && types.Contains(edge.Type)
                select new GraphPath
                {
                    FromId = edge.FromId,
                    Path = Sql.Ext.PostgreSQL().NewArray(edge.FromId),
                    ToId = edge.ToId,
                    Type = edge.Type,
                    Depth = 1
                }
            )
            .OrderByDescending(e => e.Type)
            .Take(limit)
        )
        .Concat
        (
            (
                from edge in Edges
                from sg in search
                where edge.FromId == sg.ToId
                    && !Sql.Ext.PostgreSQL().ArrayContains(sg.Path, edge.FromId)
                    && types.Contains(edge.Type)
                    && sg.Depth <= depth
                select new GraphPath
                {
                    FromId = edge.FromId,
                    Path = Sql.Ext.PostgreSQL().ArrayAppend(sg.Path, edge.FromId),
                    ToId = edge.ToId,
                    Type = edge.Type,
                    Depth = sg.Depth + 1
                }
            )
            .OrderByDescending(e => e.Type)
            .Take(limit)
        )
    )
    .Select(x => new GraphPath
    {
        Depth = x.Depth,
        FromId = x.FromId,
        Path = Sql.Ext.PostgreSQL().ArrayAppend(x.Path, x.ToId),
        Type = x.Type,
        ToId = x.ToId
    });

    return graphSte;
}

Version 3 did not work even once, I found out that this is related to the return type IQueryable<GraphPath>, if you use a primitive return type, then SQL is generated, but, of course, it does not work correctly. Is there any way to use Sql.Extension with an IQueryable<T> return type?

Versions 1,2,4 work as separate queries of the form

var graph = db.SearchGraph(node, types, 10, 10).ToList();

But they do not work inside other requests, as in the example above.

Tell me which way should I go to implement this? And one more thing, is it possible to combine EF Core DbContext and LinqToDbContext in one request as it is done in versions 2 and 4?

Issue Analytics

  • State:open
  • Created 2 years ago
  • Comments:9 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
sdanylivcommented, Nov 10, 2021

Well, finally I see what happened. Will figure out how to solve that elegantly. But actually problem in your code.

1reaction
sdanylivcommented, Nov 8, 2021

Will prepare sample tomorrow, sorry for delay. It is strange that CTE won’t work.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Mixing raw SQL with IQueryable for dynamic filter
In entity framework 6 is it possible to mix raw SQL generated dynamically with IQueryable like this: IQueryable<Tree> tree_query = context.Trees ...
Read more >
combining raw sql with IQueryable
=> so i want to execute both queries in the same statement, because if the first query yields many results i get in...
Read more >
Executing Raw SQL Queries using FromSql Method
The FromSql method in Entity Framework Core is used to execute raw SQL queries against the database and return the results as entities....
Read more >
You can now return unmapped types from raw SQL select ...
To write a raw SQL query with EF 8, you can use the SqlQuery<TResult> or SqlQueryRaw<TResult> methods. Both methods return the IQueryable< ...
Read more >
Execute Raw SQL Query in Entity Framework 6
SqlQuery() method to write raw SQL queries which return entity instances. The resulted entities will be tracked by the context, as if they...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found