2

I have the following data:

accounts

I have the following T-SQL statement:

with acc as 
(
    select 
        acc_number,
        acc_parent,
        acc_number as [root],
        balance
    from 
        accounts
    where 
        acc_parent is null

    union all

    select 
        a.acc_number,
        a.acc_parent,
        acc.[root],
        a.balance
    from 
        accounts a
    inner join 
        acc on a.acc_parent = acc.acc_number
    where 
        a.acc_parent is not null
)
select 
    max([root]) as acc_number,
    sum(balance) as balance
from 
    acc
group by 
    [root];

I'd like to translate this into a Linq expression.

My attempt was

using (AccountsContext context = new AccountsContext())
{
    var roots = context.Accounts
        .Where(r => r.acc_parent == null)
        .Select(r => new
        {
            r.acc_number, 
            r.acc_parent, 
            r.balance
        });
    var accounts = context.Accounts
            .Where(a => a.acc_parent != null)
            .Select(a => new
            {
                a.acc_number, 
                a.acc_parent, 
                a.balance
            });
    var union = roots.Union(accounts)
        .Select(u => new
        {
            u.acc_number,
            u.acc_parent,
            u.balance
        });
    var join = union.Join(roots, u => u.acc_parent, r => r.acc_number, (a, r) => new {a, r})
        .Select(t => new
        {
            t.a.acc_number,
            t.a.acc_parent,
            root = t.r.acc_number,
            t.a.balance
        });
    var all = join.GroupBy(g => new { g.root }) // edited
                .Select(g => new
                {
                    acc_number = g.Key.root,
                    balance = g.Sum(x => x.balance ?? 0)
                })
        .ToList();
}

where Account is:

public class Account
{
    public Account()
    {
    }

    [Key]
    public string acc_number { get; set; }
    public decimal? balance { get; set; }

    public string acc_parent { get; set; }
    public virtual Account parent { get; set; }

    public virtual ICollection<Account> accounts { get; set; }
}

And AccountsContext is:

public class AccountsContext : DbContext
{
    public AccountsContext() : base("localhost")
    {
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<Account>()
            .HasOptional(e => e.parent)
            .WithMany(e => e.accounts)
            .HasForeignKey(e => e.acc_parent);
    }

    public DbSet<Account> Accounts { get; set; }
}

The expected result should be:

01 > 200
05 > 100
07 > 230

When I use the SQL statement, I get the results correctly but when I use the Linq expression, It misses one account, account 04, because I get:

01 => 100
05 => 100
07 => 230
  • 2
    There is no CTE equivalent in EF. you can use execute arbitrary queries though https://msdn.microsoft.com/en-us/library/system.data.entity.database.sqlquery(v=vs.113).aspx – TheGeneral Apr 07 '18 at 08:40
  • I'm aware of Database.SqlQuery but are the changes tracked by the context? –  Apr 07 '18 at 08:51
  • Well, I tried it with SqlQuery. This got the correct result, of course, because it uses the T-SQL query, but unfortunately, it doesn't seem to support anonymous types. This was a job interview question. I'm not sure if there is another way to achieve this with EF. Thanks anyway for your reply. –  Apr 07 '18 at 09:19
  • 3
    This is not just CTE, but a recursive CTE (`acc` definition references to itself). As far as I know - EF Core is not able to emulate recursive query. So a way to go in such case is custom view\ stored procedure \ raw sql query. – Evk Apr 07 '18 at 10:04
  • Perhaps `var union = roots.Union(accounts)` should be using `var union = roots.Concat(accounts)` as your TSQL is using `union all` ? – sgmoore Apr 07 '18 at 11:37
  • @Evk Exactly. It is a recursive CTE. I'm not sure if EF supports or does not support it because apparently there is a way to do it, but I'm not aware of it, using EF. As I said, this is an interview question. I'm just curious to know. –  Apr 07 '18 at 14:09
  • @sgmoore I'll give it a try in a minute –  Apr 07 '18 at 14:10
  • @sgmoore Nop, does not affect the results. I edited the last part though: var all = join.GroupBy but I tried both union and concat. The result is the same –  Apr 07 '18 at 14:31
  • And why you are so sure it should be possible using EF and linq? That being interview question does not imply that. – Evk Apr 07 '18 at 15:21
  • @Evk I'm not sure but it doesn't make sense. Maybe I'll know in a couple of days. If there is a way, I'll post it here. I'm curious to know. Thanks –  Apr 07 '18 at 15:44
  • Interviewer could as well waited for you to say "hey, it's recursive CTE, EF core doesn't support them via linq". – Evk Apr 07 '18 at 15:46
  • @Evk Well, like you I thought it's not possible but no he didn't. I actually created it using t-sql because I wasn't aware of a way to do it in EF but he requested an ef equivalent claiming the code should be portable to any other DB. This is why I don't think it makes sense. He is expecting a way with EF. –  Apr 07 '18 at 15:50
  • From last statement it seems that interviewer did not ask you to convert this exact statement to linq. Instead he had some task, which you now represent to us with this tsql. It's not the same, since that task (we don't know about) might have different solution. – Evk Apr 07 '18 at 15:54
  • He asked to use entityframework. I ignored this and made the t-sql above. He accepted the t-sql but requested an ef solution, which is the main task. The t-sql is mine, not included in his question. –  Apr 07 '18 at 16:04
  • 1
    I understand, but you didn't tell us what was the original task given to you by interviewer. Your question states "I want to convert this tsql to EF LINQ". I claim it's not possible in current version of EF, then you are saying there should be a way, because interviewer expected solution. But problem from interview might have another solution in EF, without recursive CTE at all. – Evk Apr 08 '18 at 13:29
  • @Evk Yes, I'm sorry it wasn't clear. I realized it after you asked. And your claim is correct. As it turns out, He knows it's not possible to use linq-to-sql because there is no recursive CTE support in EF. He wanted me to get the objects and then use linq to object on the collection but he didn't specify this in the question. It was vague! That's why it didn't seems logical because he made me think there is a solution that I\m not aware of using EF. I appreciate your help very much, Thank you. –  Apr 08 '18 at 17:28

0 Answers0