18

当我尝试一次添加一个具有多个子级的实体时,我遇到了 EF 重新排序插入的问题。Outer 1--* Item 1--* SubItem我有一个 3 级结构,每个 ( )之间具有一对多的关系。如果我尝试插入带有项目和子项目的新外部,则包含子项目的项目最终会被首先插入。

示例代码(.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
    }
}

我知道Alex James 的这个回答,其中指出插入可能需要重新排序以满足关系约束,但这不是这里的问题。他的回答还提到他们无法在诸如列表之类的保持顺序的结构中跟踪项目的顺序。

我想知道如何订购这些插件。虽然我可以依靠 PK 以外的字段对插入的项目进行排序,但如果我可以依靠 PK 顺序,效率会高得多。我真的不想使用多个 SaveChanges 调用来完成此操作。

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

4

7 回答 7

10

我想知道如何订购这些插件。

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

根据评论编辑:

是的,这是低级交互,因为在使用不受您控制的抽象时,您对 SQL 命令的顺序寄予厚望。在高层次上,您会得到不同的东西,因为您使用的期望不适用于该抽象。如果您想控制 SQL 命令的顺序,您必须通过一项一项(=> 多个SaveChangesTransactionScope)保存项目来强制执行 EF,或者自己编写 SQL。否则使用单独的列进行排序。

顺便提一句。如您所见,EF 不会保存实体。它有自己的更改跟踪器,保存对所有附加实例的引用。引用保存在多个Dictionary实例中,字典不保留插入顺序。如果这些集合用于生成 SQL 命令(我猜它们是),则无法保证顺序。

于 2012-07-17T11:50:32.383 回答
2

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

如果您真的希望以特定顺序插入行,多个 SaveChanges 是您最好的选择。

没有人愿意多次调用 SaveChanges 的原因是因为这感觉就是这样:一个肮脏的 hack。

由于主键是一个技术概念,因此在此键上对结果进行排序应该没有任何功能意义。您可以按特定字段对结果进行排序,并为此使用数据库索引。您可能不会看到速度上的差异。

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

于 2012-07-17T11:18:04.157 回答
1

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

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

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

  dbContextTransaction.Commit();
}
于 2020-05-05T13:42:52.087 回答
1

另一种方法是通过实体状态更改的组合,在添加每个条目后无需数据库往返(尽管严重依赖于应用程序逻辑)。

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

所以我有一个根节点,没有提供父 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可能会有所帮助。

于 2020-09-13T00:51:38.537 回答
0

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

于 2019-02-14T14:20:15.650 回答
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
    }
]

数据上下文种子文件

    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);
                }
            }
   }
于 2020-10-20T16:53:15.823 回答
0

废话!

这个问题是 9 年前提出的,微软仍然没有做任何事情。

在 EF6 中,我发现如果我使用

for(int i=0; i<somearray.Length; i++)
   db.Table.Add(new Item {Name = somearray[i]});

它按顺序执行,但如果我使用
foreach(var item in somearry)
   db.Table.Add(new Item {Name = item})
则不按顺序。

当我使用 EF core 3.1 时,总是不符合上述两种情况的顺序。

于 2021-09-10T14:47:30.560 回答