EF Core - Enforce priority in executing commands in a transaction










2














I want to delete 2 set of data in database, using EF Core.

All codes are hypothetical.



Data models:



class Parent

public int Id get; set;


class Child

public int Id get; set;

public int ParentId get; set;

public virtual Parent Parent get; set;

public bool Flag get; set;



Let's assume I want to delete all [Child] records with (ParentId=100) and (flag=false), after that if (child.ParentId=100).length=0 then delete the parent itself too.

So, here is the service class:



class Service

public void Command(int parentId)

Parent parent = GetParent(parentId);
List<Child> children = GetChildren(parent);

List<Child> toDelete = children.Where(x => !x.Flag).ToList();
foreach(var child in toDelete)

var entry = DbContext.Entry(child);
entry.State = EntityState.Deleted;


List<Child> remainChildren = children.Where(x => x.Flag).ToList();
if (!remainChildren.Any())

var entry = DbContext.Entry(parent );
entry.State = EntityState.Deleted;


SaveChanges();




I have multiple scenarios that call the Service.Command method.

Because I call SaveChanges() only once, I assume that all delete operations will be executed in a single transaction, and of course they would be in this order:



  1. Delete child records

  2. Delete parent

but EF send queries to database like this:



  1. Delete parent

  2. Delete child records

Obviously it will throw an ForeignKey exception.



Is there any way to enforce EF Core to execute queries in order that I wrote the code?










share|improve this question























  • What do your repository and SetState methods do? Also why are you "getting" the children twice?
    – Wurd
    Nov 12 at 9:50











  • @Wurd I changed the code to be more clear. Please take a look again.
    – Mohammad Azhdari
    Nov 12 at 9:59











  • Instead of manually deleting child records use OnDelete(DeleteBehavior.Cascade). For required relations that's the default behaviour. Cascading works even with untracked entities
    – Panagiotis Kanavos
    Nov 12 at 10:27











  • BTW the question's code doesn't delete entities - there's no call to DbContext.Remove which means EF doesn't know anything about the order. The code treats all entities as if they were detached, even though GetChildren would have to load child records from the database. I suspect GetChildren is a read-only method incorrectly used to remove entities from the database.
    – Panagiotis Kanavos
    Nov 12 at 10:32















2














I want to delete 2 set of data in database, using EF Core.

All codes are hypothetical.



Data models:



class Parent

public int Id get; set;


class Child

public int Id get; set;

public int ParentId get; set;

public virtual Parent Parent get; set;

public bool Flag get; set;



Let's assume I want to delete all [Child] records with (ParentId=100) and (flag=false), after that if (child.ParentId=100).length=0 then delete the parent itself too.

So, here is the service class:



class Service

public void Command(int parentId)

Parent parent = GetParent(parentId);
List<Child> children = GetChildren(parent);

List<Child> toDelete = children.Where(x => !x.Flag).ToList();
foreach(var child in toDelete)

var entry = DbContext.Entry(child);
entry.State = EntityState.Deleted;


List<Child> remainChildren = children.Where(x => x.Flag).ToList();
if (!remainChildren.Any())

var entry = DbContext.Entry(parent );
entry.State = EntityState.Deleted;


SaveChanges();




I have multiple scenarios that call the Service.Command method.

Because I call SaveChanges() only once, I assume that all delete operations will be executed in a single transaction, and of course they would be in this order:



  1. Delete child records

  2. Delete parent

but EF send queries to database like this:



  1. Delete parent

  2. Delete child records

Obviously it will throw an ForeignKey exception.



Is there any way to enforce EF Core to execute queries in order that I wrote the code?










share|improve this question























  • What do your repository and SetState methods do? Also why are you "getting" the children twice?
    – Wurd
    Nov 12 at 9:50











  • @Wurd I changed the code to be more clear. Please take a look again.
    – Mohammad Azhdari
    Nov 12 at 9:59











  • Instead of manually deleting child records use OnDelete(DeleteBehavior.Cascade). For required relations that's the default behaviour. Cascading works even with untracked entities
    – Panagiotis Kanavos
    Nov 12 at 10:27











  • BTW the question's code doesn't delete entities - there's no call to DbContext.Remove which means EF doesn't know anything about the order. The code treats all entities as if they were detached, even though GetChildren would have to load child records from the database. I suspect GetChildren is a read-only method incorrectly used to remove entities from the database.
    – Panagiotis Kanavos
    Nov 12 at 10:32













2












2








2


1





I want to delete 2 set of data in database, using EF Core.

All codes are hypothetical.



Data models:



class Parent

public int Id get; set;


class Child

public int Id get; set;

public int ParentId get; set;

public virtual Parent Parent get; set;

public bool Flag get; set;



Let's assume I want to delete all [Child] records with (ParentId=100) and (flag=false), after that if (child.ParentId=100).length=0 then delete the parent itself too.

So, here is the service class:



class Service

public void Command(int parentId)

Parent parent = GetParent(parentId);
List<Child> children = GetChildren(parent);

List<Child> toDelete = children.Where(x => !x.Flag).ToList();
foreach(var child in toDelete)

var entry = DbContext.Entry(child);
entry.State = EntityState.Deleted;


List<Child> remainChildren = children.Where(x => x.Flag).ToList();
if (!remainChildren.Any())

var entry = DbContext.Entry(parent );
entry.State = EntityState.Deleted;


SaveChanges();




I have multiple scenarios that call the Service.Command method.

Because I call SaveChanges() only once, I assume that all delete operations will be executed in a single transaction, and of course they would be in this order:



  1. Delete child records

  2. Delete parent

but EF send queries to database like this:



  1. Delete parent

  2. Delete child records

Obviously it will throw an ForeignKey exception.



Is there any way to enforce EF Core to execute queries in order that I wrote the code?










share|improve this question















I want to delete 2 set of data in database, using EF Core.

All codes are hypothetical.



Data models:



class Parent

public int Id get; set;


class Child

public int Id get; set;

public int ParentId get; set;

public virtual Parent Parent get; set;

public bool Flag get; set;



Let's assume I want to delete all [Child] records with (ParentId=100) and (flag=false), after that if (child.ParentId=100).length=0 then delete the parent itself too.

So, here is the service class:



class Service

public void Command(int parentId)

Parent parent = GetParent(parentId);
List<Child> children = GetChildren(parent);

List<Child> toDelete = children.Where(x => !x.Flag).ToList();
foreach(var child in toDelete)

var entry = DbContext.Entry(child);
entry.State = EntityState.Deleted;


List<Child> remainChildren = children.Where(x => x.Flag).ToList();
if (!remainChildren.Any())

var entry = DbContext.Entry(parent );
entry.State = EntityState.Deleted;


SaveChanges();




I have multiple scenarios that call the Service.Command method.

Because I call SaveChanges() only once, I assume that all delete operations will be executed in a single transaction, and of course they would be in this order:



  1. Delete child records

  2. Delete parent

but EF send queries to database like this:



  1. Delete parent

  2. Delete child records

Obviously it will throw an ForeignKey exception.



Is there any way to enforce EF Core to execute queries in order that I wrote the code?







c# entity-framework asp.net-core ef-core-2.1






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 12 at 9:58

























asked Nov 12 at 9:46









Mohammad Azhdari

25729




25729











  • What do your repository and SetState methods do? Also why are you "getting" the children twice?
    – Wurd
    Nov 12 at 9:50











  • @Wurd I changed the code to be more clear. Please take a look again.
    – Mohammad Azhdari
    Nov 12 at 9:59











  • Instead of manually deleting child records use OnDelete(DeleteBehavior.Cascade). For required relations that's the default behaviour. Cascading works even with untracked entities
    – Panagiotis Kanavos
    Nov 12 at 10:27











  • BTW the question's code doesn't delete entities - there's no call to DbContext.Remove which means EF doesn't know anything about the order. The code treats all entities as if they were detached, even though GetChildren would have to load child records from the database. I suspect GetChildren is a read-only method incorrectly used to remove entities from the database.
    – Panagiotis Kanavos
    Nov 12 at 10:32
















  • What do your repository and SetState methods do? Also why are you "getting" the children twice?
    – Wurd
    Nov 12 at 9:50











  • @Wurd I changed the code to be more clear. Please take a look again.
    – Mohammad Azhdari
    Nov 12 at 9:59











  • Instead of manually deleting child records use OnDelete(DeleteBehavior.Cascade). For required relations that's the default behaviour. Cascading works even with untracked entities
    – Panagiotis Kanavos
    Nov 12 at 10:27











  • BTW the question's code doesn't delete entities - there's no call to DbContext.Remove which means EF doesn't know anything about the order. The code treats all entities as if they were detached, even though GetChildren would have to load child records from the database. I suspect GetChildren is a read-only method incorrectly used to remove entities from the database.
    – Panagiotis Kanavos
    Nov 12 at 10:32















What do your repository and SetState methods do? Also why are you "getting" the children twice?
– Wurd
Nov 12 at 9:50





What do your repository and SetState methods do? Also why are you "getting" the children twice?
– Wurd
Nov 12 at 9:50













@Wurd I changed the code to be more clear. Please take a look again.
– Mohammad Azhdari
Nov 12 at 9:59





@Wurd I changed the code to be more clear. Please take a look again.
– Mohammad Azhdari
Nov 12 at 9:59













Instead of manually deleting child records use OnDelete(DeleteBehavior.Cascade). For required relations that's the default behaviour. Cascading works even with untracked entities
– Panagiotis Kanavos
Nov 12 at 10:27





Instead of manually deleting child records use OnDelete(DeleteBehavior.Cascade). For required relations that's the default behaviour. Cascading works even with untracked entities
– Panagiotis Kanavos
Nov 12 at 10:27













BTW the question's code doesn't delete entities - there's no call to DbContext.Remove which means EF doesn't know anything about the order. The code treats all entities as if they were detached, even though GetChildren would have to load child records from the database. I suspect GetChildren is a read-only method incorrectly used to remove entities from the database.
– Panagiotis Kanavos
Nov 12 at 10:32




BTW the question's code doesn't delete entities - there's no call to DbContext.Remove which means EF doesn't know anything about the order. The code treats all entities as if they were detached, even though GetChildren would have to load child records from the database. I suspect GetChildren is a read-only method incorrectly used to remove entities from the database.
– Panagiotis Kanavos
Nov 12 at 10:32












1 Answer
1






active

oldest

votes


















1














Set the parent child relationship to cascade delete at the DB level.



Query the needed data in one hit...



var data = context.Parents.Where(p => p.ParentId == parentId)
.Select(p => new

Parent = p,
ChildrenToRemove = p.Children.Where(c => c.Flag).ToList(),
HasRemainingChildren = p.Children.Any(c => !c.Flag)
).Single();


Then it's just a matter of inspecting the data and acting accordingly. If there are no remaining children, delete the parent and let cascade take care of it. Otherwise, just delete the children from the context.



if(!data.HasRemainingChildren)
context.Parents.Remove(data.Parent);
else
context.Children.RemoveRange(data.ChildrenToRemove);


For big entities you can further optimize this by selecting just the IDs then associating them to new Entity instances, attach them to a fresh DbContext, and then issue the Remove/RemoveRange calls. This option is an optimization for dealing with large numbers of items, or "big" entities that would otherwise result in a lot of data across the wire.






share|improve this answer




















    Your Answer






    StackExchange.ifUsing("editor", function ()
    StackExchange.using("externalEditor", function ()
    StackExchange.using("snippets", function ()
    StackExchange.snippets.init();
    );
    );
    , "code-snippets");

    StackExchange.ready(function()
    var channelOptions =
    tags: "".split(" "),
    id: "1"
    ;
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function()
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled)
    StackExchange.using("snippets", function()
    createEditor();
    );

    else
    createEditor();

    );

    function createEditor()
    StackExchange.prepareEditor(
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader:
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    ,
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    );



    );













    draft saved

    draft discarded


















    StackExchange.ready(
    function ()
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53259488%2fef-core-enforce-priority-in-executing-commands-in-a-transaction%23new-answer', 'question_page');

    );

    Post as a guest















    Required, but never shown

























    1 Answer
    1






    active

    oldest

    votes








    1 Answer
    1






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    1














    Set the parent child relationship to cascade delete at the DB level.



    Query the needed data in one hit...



    var data = context.Parents.Where(p => p.ParentId == parentId)
    .Select(p => new

    Parent = p,
    ChildrenToRemove = p.Children.Where(c => c.Flag).ToList(),
    HasRemainingChildren = p.Children.Any(c => !c.Flag)
    ).Single();


    Then it's just a matter of inspecting the data and acting accordingly. If there are no remaining children, delete the parent and let cascade take care of it. Otherwise, just delete the children from the context.



    if(!data.HasRemainingChildren)
    context.Parents.Remove(data.Parent);
    else
    context.Children.RemoveRange(data.ChildrenToRemove);


    For big entities you can further optimize this by selecting just the IDs then associating them to new Entity instances, attach them to a fresh DbContext, and then issue the Remove/RemoveRange calls. This option is an optimization for dealing with large numbers of items, or "big" entities that would otherwise result in a lot of data across the wire.






    share|improve this answer

























      1














      Set the parent child relationship to cascade delete at the DB level.



      Query the needed data in one hit...



      var data = context.Parents.Where(p => p.ParentId == parentId)
      .Select(p => new

      Parent = p,
      ChildrenToRemove = p.Children.Where(c => c.Flag).ToList(),
      HasRemainingChildren = p.Children.Any(c => !c.Flag)
      ).Single();


      Then it's just a matter of inspecting the data and acting accordingly. If there are no remaining children, delete the parent and let cascade take care of it. Otherwise, just delete the children from the context.



      if(!data.HasRemainingChildren)
      context.Parents.Remove(data.Parent);
      else
      context.Children.RemoveRange(data.ChildrenToRemove);


      For big entities you can further optimize this by selecting just the IDs then associating them to new Entity instances, attach them to a fresh DbContext, and then issue the Remove/RemoveRange calls. This option is an optimization for dealing with large numbers of items, or "big" entities that would otherwise result in a lot of data across the wire.






      share|improve this answer























        1












        1








        1






        Set the parent child relationship to cascade delete at the DB level.



        Query the needed data in one hit...



        var data = context.Parents.Where(p => p.ParentId == parentId)
        .Select(p => new

        Parent = p,
        ChildrenToRemove = p.Children.Where(c => c.Flag).ToList(),
        HasRemainingChildren = p.Children.Any(c => !c.Flag)
        ).Single();


        Then it's just a matter of inspecting the data and acting accordingly. If there are no remaining children, delete the parent and let cascade take care of it. Otherwise, just delete the children from the context.



        if(!data.HasRemainingChildren)
        context.Parents.Remove(data.Parent);
        else
        context.Children.RemoveRange(data.ChildrenToRemove);


        For big entities you can further optimize this by selecting just the IDs then associating them to new Entity instances, attach them to a fresh DbContext, and then issue the Remove/RemoveRange calls. This option is an optimization for dealing with large numbers of items, or "big" entities that would otherwise result in a lot of data across the wire.






        share|improve this answer












        Set the parent child relationship to cascade delete at the DB level.



        Query the needed data in one hit...



        var data = context.Parents.Where(p => p.ParentId == parentId)
        .Select(p => new

        Parent = p,
        ChildrenToRemove = p.Children.Where(c => c.Flag).ToList(),
        HasRemainingChildren = p.Children.Any(c => !c.Flag)
        ).Single();


        Then it's just a matter of inspecting the data and acting accordingly. If there are no remaining children, delete the parent and let cascade take care of it. Otherwise, just delete the children from the context.



        if(!data.HasRemainingChildren)
        context.Parents.Remove(data.Parent);
        else
        context.Children.RemoveRange(data.ChildrenToRemove);


        For big entities you can further optimize this by selecting just the IDs then associating them to new Entity instances, attach them to a fresh DbContext, and then issue the Remove/RemoveRange calls. This option is an optimization for dealing with large numbers of items, or "big" entities that would otherwise result in a lot of data across the wire.







        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered Nov 12 at 10:42









        Steve Py

        5,19511017




        5,19511017



























            draft saved

            draft discarded
















































            Thanks for contributing an answer to Stack Overflow!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid


            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.

            To learn more, see our tips on writing great answers.





            Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


            Please pay close attention to the following guidance:


            • Please be sure to answer the question. Provide details and share your research!

            But avoid


            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.

            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53259488%2fef-core-enforce-priority-in-executing-commands-in-a-transaction%23new-answer', 'question_page');

            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            這個網誌中的熱門文章

            How to read a connectionString WITH PROVIDER in .NET Core?

            Node.js Script on GitHub Pages or Amazon S3

            Museum of Modern and Contemporary Art of Trento and Rovereto