Calling raw SQL with return type IQueryable<T>
See original GitHub issueGood 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:
- Created 2 years ago
- Comments:9 (4 by maintainers)
Top GitHub Comments
Well, finally I see what happened. Will figure out how to solve that elegantly. But actually problem in your code.
Will prepare sample tomorrow, sorry for delay. It is strange that CTE won’t work.