Search code examples
.netvb.netlinqado.netlinq-to-dataset

How can I get "levels" of a self referencing table in LINQ?


There's a table Category with a pk idCategory and a self-referencing foreign-key fiCategory. That means categories are "main-categories" when fiCategory is null. If fiCategory links to another category, it is a sub-category of it. But it's also valid that this sub-category also has 1-n sub-categories(with fiCategory linking to it's idCategory).

Q: How can i get a list of main-categories,sub-categories,"sub-sub-categories", ...etc. with LINQ?

Backgound:

I'm using typed DataSets to compare data from Server1/MySQL with data from Server2/MS SQL-Server. After normalizing and cleaning(there are several inconsistencies) i want to import the new data into SQL-Server. First of all i have to import the main-categories, then the sub-categories and so on. Otherwise SQL-Server would throw a constraint exception when i would try to insert a row with a foreign-key to a category that is yet not inserted.

These are the tables(left MySQL-source, right SQL-Server destination table):

Kategorie: MySQL-source Category: SQL-Server destination

Here i'm getting the new rows in MySQL that are not in SQL-Server:

src and dest are typed DataSets

Dim idSrc = From c In src.kategorie Select c.kategorie_id
Dim idDest = From c In dest.Category Select c.idCategory
Dim diff = idSrc.Except(idDest)
Dim needUpdate = diff.Any

Now i want to import the new rows. In this way i get all "main-categories":

Dim mainCat = From kat In src.kategorie
            Join d In diff
            On kat.kategorie_id Equals d
            Where kat.IsparentNull
        Select kat

For Each cat In mainCat
    Dim newCat = Me.dest.Category.NewCategoryRow
    newCat.idCategory = cat.kategorie_id
    newCat.Name = cat.name
    newCat.SetfiCategoryNull()
    dest.Category.AddCategoryRow(newCat)
    rowsUpdated += daCategoryOut.Update(dest.Category)
Next

In this way i get all sub-categories:

Dim subCat = From kat In src.kategorie
               Join d In diff
               On kat.kategorie_id Equals d
               Where Not kat.IsparentNull
          Select kat

Both LINQ-queries are working, but how do i get all "levels" of sub-categories? I need to insert the rows from "top" to "bottom". Is there a way that works even with any depth?

At least this is not working(repeating pk-values):

Dim subCatWithChild = From cat In subCat
                      Join child In 
                     (From kat In src.kategorie Where Not kat.IsparentNull)
                      On child.parent Equals cat.kategorie_id
                   Select cat

I'm still learning LINQ and appreciating any kind of suggestions(also in C#). Thank you in advance.

Note: Maybe you know a way i can temporarily disable the foreign key contraint in SQL-Server and enable it after i inserted all rows from ADO.NET. That would be much simpler.


This is the solution, thanks to @Tridus:

Dim mainCat = From kat In src.kategorie
          Where kat.IsparentNull
      Select kat

For Each kat In mainCat
   rowsUpdated += insertCategory(kat, diff, daCategoryOut)
Next

This is the recursive function:

Private Function insertCategory(ByVal parent As CC_IN.kategorieRow, ByVal diff As IEnumerable(Of Int32), ByVal daCategoryOut As CC_OutTableAdapters.CategoryTableAdapter) As Int32
    Dim rowsInserted As Int32 = 0

    If diff.Contains(parent.kategorie_id) Then
        Dim newCat = Me.dest.Category.NewCategoryRow
        newCat.idCategory = parent.kategorie_id
        newCat.Name = parent.name
        If parent.IsparentNull Then
            newCat.fiCategory = parent.parent
        Else
            newCat.SetfiCategoryNull()
        End If
        dest.Category.AddCategoryRow(newCat)
        rowsInserted += daCategoryOut.Update(dest.Category)
    End If

    'get all childs from this parent
    Dim childs = From cat In Me.src.kategorie
               Where Not cat.IsparentNull AndAlso cat.parent = parent.kategorie_id
               Select cat
    'insert all childs for this parent
    For Each child In childs
        rowsInserted += insertCategory(child, diff, daCategoryOut)
    Next

    Return rowsInserted
End Function

Solution

  • Yes, Foreign Key constraints can be temporarily disabled.

    The best way to do this (other then disabling foreign keys and just copying the entire table row-by-row) is recursively starting with the main categories. Conceptually, you'd do this:

    Get the main categories (which you've done)
    For each main category
       Is this one in the other DB? If not, add it.
       Get the sub-categories of this main category.
       For each sub-category
         Is this one in the other DB? If not, add it.
         Get the sub-categories of this sub-category.
    

    etc. It's easy to get the sub-categories of whatever category you currently have, so if you just start at the top you can walk the entire tree and add anything that's missing on the other side.