实体框架中多个记录的插入顺序

时间:2012-07-17 10:56:42

标签: entity-framework entity-framework-5

当我尝试一次添加一个包含多个子节点的实体时,我在重新排序插件时遇到问题。我有一个3级结构,每个结构之间有一对多的关系(Outer 1--* Item 1--* SubItem)。如果我尝试插入一个带有Items和Subitems的新Outer,那么包含SubItems的Items最终会被插入。

示例代码(.NET 4.5,EF 5.0.0-rc):

public class Outer
{
    public int OuterId { get; set; }
    public virtual IList<Item> Items { get; set; }
}

public class Item
{
    public int OuterId { get; set; }
    [ForeignKey("OuterId")]
    public virtual Outer Outer { get; set; }

    public int ItemId { get; set; }
    public int Number { get; set; }

    public virtual IList<SubItem> SubItems { get; set; }
}

public class SubItem
{
    public int SubItemId { get; set; }

    [ForeignKey("ItemId")]
    public virtual Item Item { get; set; }
    public int ItemId { get; set; }
}

public class MyContext : DbContext
{
    public DbSet<Outer> Outers { get; set; }
    public DbSet<Item> Items { get; set; }
    public DbSet<SubItem> SubItems { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());
        MyContext context = new MyContext();

        // Add an Outer object, with 3 Items, the middle one having a subitem
        Outer outer1 = new Outer { Items = new List<Item>() };
        context.Outers.Add(outer1);
        outer1.Items.Add(new Item { Number = 1, SubItems = new List<SubItem>() });
        outer1.Items.Add(new Item { Number = 2, SubItems = new List<SubItem>(new SubItem[] { new SubItem() }) });
        outer1.Items.Add(new Item { Number = 3, SubItems = new List<SubItem>() });

        context.SaveChanges();

        // Print the order these have ended up in
        foreach (Item item in context.Items)
        {
            Console.WriteLine("{0}\t{1}", item.ItemId, item.Number);
        }
        // Produces output:
        // 1       2
        // 2       1
        // 3       3
    }
}

我知道this answer by Alex James指出插入可能需要重新排序以满足关系约束,但这不是问题。他的回答还提到他们无法跟踪保留订单的结构中的项目顺序,如列表。

我想知道的是我如何才能订购这些刀片。虽然我可以依靠通过除PK以外的字段对插入的项目进行排序,但如果我可以依赖PK顺序,它会更有效率。我真的不想使用多个SaveChanges调用来完成此任务。

我正在使用EF5 RC,但从其他未解答的问题来看,这已经有一段时间了!

6 个答案:

答案 0 :(得分:7)

  

我想知道的是如何才能订购这些刀片。

你做不到。数据库命令的顺序是EF的内部行为。如果要控制命令的顺序,请不要使用从低级数据库交互中抽象出来的工具 - 直接使用SQL。

根据评论进行编辑:

是的,它是低级别的交互,因为在使用您无法控制的抽象时,您会对SQL命令的顺序产生期望。在高层次上,你会得到不同的东西,因为你正在使用那些不能用于抽象的期望。如果要控制SQL命令的顺序,则必须通过逐个保存项目来强制执行EF(=&gt;多个SaveChangesTransactionScope),或者自己编写SQL。否则使用单独的列进行排序。

顺便说一下。 EF不会像您看到的那样保存实体。它有自己的更改跟踪器,包含对所有附加实例的引用。引用包含在多个Dictionary实例和字典doesn't preserve insertion order中。如果这些集合用于生成SQL命令(我猜它们是),则无法保证订单。

答案 1 :(得分:1)

数据库中的表是集合。这意味着订单无法保证。我假设您的示例中您希望按“数字”排序结果。如果这是您想要的,如果该数字发生变化并且它不再反映数据库中的顺序,您打算做什么?

如果你真的想按特定顺序插入行,那么多个SaveChanges是你最好的选择。

没有人想多次调用SaveChanges的原因是因为它确实是这样的:一个肮脏的黑客。

由于主键是一种技术概念,因此无论如何都不应该在此键上订购结果。您可以按特定字段对结果进行排序,并为此使用数据库索引。你可能不会看到速度上的差异。

明确订购还有其他好处: 对于必须维护它的人来说,它更容易理解。否则,该人必须知道主键上的排序很重要并给出正确的结果,因为在您的应用程序的其他(完全)不相关的部分中,它意外地与数字字段的顺序相同。

答案 2 :(得分:1)

我找到了一种方法。只是以为我会让你知道:

using (var dbContextTransaction = dbContext.Database.BeginTransaction())
{
  dbContext.SomeTables1.Add(object1);
  dbContext.SaveChanges();

  dbContext.SomeTables1.Add(object2);
  dbContext.SaveChanges();

  dbContextTransaction.Commit();
}

答案 3 :(得分:0)

保存之前的

多个Add()或保存之前的AddRange()不会保留顺序。同样,在阅读集合时,也不能保证以最初添加时的顺序返回结果。您需要向实体添加一些属性,并且在查询时使用OrderBy()来确保它们以所需的顺序返回。

答案 4 :(得分:0)

另一种方法是,通过添加实体状态更改来在添加每个条目(但在很大程度上取决于应用程序逻辑)之后不进行数据库往返操作。

在我的情况下-节点层次结构-我必须先保留根节点,然后再保留其余层次结构,以便自动路径计算起作用。

所以我有一个 root 节点,没有提供父ID,而提供了父ID的子节点。

EF Core随机地(或通过复杂和智能的逻辑-您喜欢:)随机安排节点插入,从而中断路径计算过程。

因此,我使用了覆盖上下文的 SaveChanges 方法,并检查了需要维护插入顺序的集合中的实体-首先分离个子节点,然后保存更改,并附加子节点并再次保存更改。

// select child nodes first - these entites should be added last
List<EntityEntry<NodePathEntity>> addedNonRoots = this.ChangeTracker.Entries<NodePathEntity>().Where(e => e.State == EntityState.Added && e.Entity.NodeParentId.HasValue == true).ToList();

// select root nodes second - these entities should be added first
List<EntityEntry<NodePathEntity>> addedRoots = this.ChangeTracker.Entries<NodePathEntity>().Where(e => e.State == EntityState.Added && e.Entity.NodeParentId.HasValue == false).ToList();

        if (!Xkc.Utilities.IsCollectionEmptyOrNull(addedRoots))
        {
            if (!Xkc.Utilities.IsCollectionEmptyOrNull(addedNonRoots))
            {
                // detach child nodes, so they will be ignored on SaveChanges call
                // no database inserts will be generated for them
                addedNonRoots.ForEach(e => e.State = EntityState.Detached);

                // run SaveChanges - since root nodes are still there, 
                // in ADDED state, inserts will be executed for these entities
                int detachedRowCount = base.SaveChanges();

                // re-attach child nodes to the context
                addedNonRoots.ForEach(e => e.State = EntityState.Added);

                // run SaveChanges second time, child nodes are saved
                return base.SaveChanges() + detachedRowCount;
            }
        }

这种方法不能让您保留各个实体的顺序,但是如果您可以将实体归类为必须先插入的实体,然后再将其插入的实体归类-此 hack 可能会有所帮助。 / p>

答案 5 :(得分:0)

这不专业。但是,我用这种方法解决了我的问题。

PublicMenu.cs文件:

public class PublicMenu
    {
        public int Id { get; set; }

        public int? Order { get; set; }
        
        public int? ParentMenuId { get; set; }

        [ForeignKey("ParentMenuId")]
        public virtual PublicMenu Parent { get; set; }

        public virtual ICollection<PublicMenu> Children { get; set; }

        public string Controller { get; set; }

        public string Action { get; set; }

        public string PictureUrl { get; set; }

        public bool Enabled { get; set; }
}

PublicMenus.json文件

[
    {
        "Order": 1,
        "Controller": "Home",
        "Action": "Index",
        "PictureUrl": "",
        "Enabled": true
    },
    {
        "Order": 2,
        "Controller": "Home",
        "Action": "Services",
        "PictureUrl": "",
        "Enabled": true
    },
    {
        "Order": 3,
        "Controller": "Home",
        "Action": "Portfolio",
        "PictureUrl": "",
        "Enabled": true
    },
    {
        "Order": 4,
        "Controller": "Home",
        "Action": "About",
        "PictureUrl": "",
        "Enabled": true
    },
    {
        "Order": 5,
        "Controller": "Home",
        "Action": "Contact",
        "PictureUrl": "",
        "Enabled": true
    }
]

DataContextSeed文件

    public class DataContextSeed
        {
            public static async Task SeedAsync(ICMSContext context, ILoggerFactory loggerFactory)
            {
                try
                {
                       if (!context.PublicMenus.Any())
                        {
                            var publicMenusData = File.ReadAllText("../Infrastructure/Data/SeedData/PublicMenus.json");
                            var publicMenus = JsonSerializer.Deserialize<List<PublicMenu>>(publicMenusData);
                            foreach (var item in publicMenus)
                            {
                                context.PublicMenus.Add(item);
                                await context.SaveChangesAsync();
                            }
                       }
                }
                catch (Exception ex)
                {
                    var logger = loggerFactory.CreateLogger<ICMSContextSeed>();
                    logger.LogError(ex.Message);
                }
            }
   }