首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >2.2.4插入项与1:n关系的错误-最佳解决方案

2.2.4插入项与1:n关系的错误-最佳解决方案
EN

Stack Overflow用户
提问于 2019-05-08 19:50:50
回答 1查看 786关注 0票数 0

我有一个Server数据库作为web服务后端,在移动前端有一个本地SQLite数据库。

这两种方法都使用了TpH方法的EFCore2.2.4,并且共享了大多数C#类,但是--当然--由于管理方面的问题,后端类具有更多的属性。为了对差异进行建模,使用了条件属性和fluent API (参见下面的代码)。

主要思想是在后端创建db内容,然后将节录存储在前端db中。

在最后(和简化),本地数据库应该只存储一个单元列表。

每个单元包含一个练习列表(n:m)。

练习是从抽象类派生出来的,一些练习类型可以包含存储在额外表中的必需工具列表(1:n)。

每个练习都是独特的,所以我可以用它的主键来表示n:m单位--运动关系。

另一方面,一个工具可能会被多个练习引用,这会给在本地前端数据库中添加练习题单元带来麻烦。

问题是,练习工具1:n映射也使用了工具的主键,因为后端就是这样创建它的,因此,如果前端上的工具的主键已经存在,则前端db Add()调用将失败。

SQLite错误19:“唯一约束失败:Tool.Id”

因此,问题是:为前端设计模型的最简单方法是什么,这样我就只能插入后端生成的单元列表。

  1. 不犯错误
  2. 不用手工做很多数据库的事情,因为EF应该为我做的

下面是示例代码

代码语言:javascript
复制
namespace Test
{
#if FRONTEND
    [Table("TestUnit")]
#endif      
    public class TestUnit
    {
        public Guid Id { get; set; } // Use values from backend

        public string Name { get; set; }

        public List<TestExerciseSequence> ExerciseSequences { get; set; }
    }

    public class TestExerciseSequence
    {
        public Guid UnitId { get; set; }
        public TestUnit Unit { get; set; }

        public Guid ExerciseId { get; set; }
        public TestExercise Exercise { get; set; }
    }


#if FRONTEND
    [Table("TestExercise")]
#endif
    public abstract class TestExercise
    {
        public Guid Id { get; set; }

        public string Name { get; set; }

        public string Discriminator { get; set; }

        public List<TestExerciseSequence> ExerciseSequences { get; set; }
    }

    public class TestExerciseType1 : TestExercise
    {
        public TestExerciseToolType ToolType { get; set; }
    }

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

        public string Name { get; set; }

        public List<TestExerciseType1> ExerciseTypes { get; set; }
    }

    public class TestLocalDBContext : DbContext
    {
        // All locally stored units
        public DbSet<TestUnit> Units { get; set; }

        private const string databaseName = "sqlite.db";
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            string databasePath;
            switch (Device.RuntimePlatform)
            {
                case Device.Android:
                    databasePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), databaseName);
                    break;
                default:
                    throw new NotImplementedException("Platform not supported");
            }
#if FRONTEND                        
            optionsBuilder.UseSqlite($"Filename={databasePath}")
                          .EnableDetailedErrors()
                          .EnableSensitiveDataLogging()
                          ;
#endif                                                  
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            // n : m relationship between 'Unit' to 'Exercise'
            modelBuilder.Entity<TestExerciseSequence>()
                .HasKey(us => new { us.UnitId, us.ExerciseId });

            modelBuilder.Entity<TestExerciseSequence>()
                .HasOne<TestUnit>(us => us.Unit)
                .WithMany(u => u.ExerciseSequences)
                .HasForeignKey(us => us.UnitId);

            modelBuilder.Entity<TestExerciseSequence>()
                .HasOne<TestExercise>(us => us.Exercise)
                .WithMany(e => e.ExerciseSequences)
                .HasForeignKey(us => us.ExerciseId);

#if FRONTEND                                
            // TestExerciseToolType causes troubles     
            modelBuilder.Entity<TestExerciseToolType>()
                .Property(c => c.Id)
                .ValueGeneratedNever();

            modelBuilder.Entity<TestExerciseToolType>()
                .HasIndex(c => c.Id)
                .IsUnique(true);
#endif

            // 1 : n relationship between 'TestExerciseType1' to 'TestExerciseToolType'
            modelBuilder.Entity<TestExerciseType1>()
                .HasOne<TestExerciseToolType>(etmt => etmt.ToolType)
                .WithMany(etmt => etmt.ExerciseTypes);

#if FRONTEND                                
            // Copied from above        
            modelBuilder.Entity<TestExerciseToolType>()
                .Property(c => c.Id)
                .ValueGeneratedNever();
            modelBuilder.Entity<TestExerciseToolType>()
                .HasIndex(c => c.Id)
                .IsUnique(true);
#endif

            // Table-per-Hierarchy for 'Exercise'
            modelBuilder.Entity<TestExercise>()
                .HasDiscriminator<string>("Discriminator")
                .HasValue<TestExerciseType1>("ExerciseTypeMovement")
                                ;

            modelBuilder.Entity<TestExercise>().Property("Discriminator").HasMaxLength(80);

            // Unit - ExerciseSequence
            modelBuilder.Entity<TestUnit>().HasMany(u => u.ExerciseSequences);
        }
    }
    public class TestDBHelper<T> where T : TestLocalDBContext
    {
        protected TestLocalDBContext CreateContext()
        {
            var dbContext = (T)Activator.CreateInstance(typeof(T));
            dbContext.Database.EnsureCreated();
            dbContext.Database.Migrate();
            return dbContext;
        }

        public void AddUnit(TestUnit u)
        {
            using (var context = CreateContext())
            {
                context.Units.Add(u);
                context.SaveChanges();
            }
        }
    }
}

和测试代码

代码语言:javascript
复制
    private void DoTest()
    {
            var uId1 = Guid.NewGuid();
            var uId2 = Guid.NewGuid();

            var eId1 = Guid.NewGuid();
            var eId2 = Guid.NewGuid();
            var eId3 = Guid.NewGuid();
            var eId4 = Guid.NewGuid();

            var u1 = new TestUnit()
            {
                    Id = uId1,
                    Name = "Unit1",
                    ExerciseSequences = new List<TestExerciseSequence>()
                    {
                            new TestExerciseSequence()
                            {
                                    UnitId = uId1,
                                    ExerciseId = eId1,
                                    Exercise = new TestExerciseType1()
                                    {
                                            Id = eId1,
                                            Discriminator = "TestExerciseType1",
                                            Name = "E1",
                                            ToolType = new TestExerciseToolType()
                                            {
                                                    Id = 1,
                                                    Name = "M1"
                                            }
                                    }
                            },
                            new TestExerciseSequence()
                            {
                                    UnitId = uId1,
                                    ExerciseId = eId2,
                                    Exercise = new TestExerciseType1()
                                    {
                                            Id = eId2,
                                            Discriminator = "TestExerciseType1",
                                            Name = "E2",
                                            ToolType = new TestExerciseToolType()
                                            {
                                                    Id = 2,
                                                    Name = "M2"
                                            }
                                    }
                            }
                    }
            };

            var u2 = new TestUnit()
            {
                    Id = uId2,
                    Name = "Unit2",
                    ExerciseSequences = new List<TestExerciseSequence>()
                    {
                            new TestExerciseSequence()
                            {
                                    UnitId = uId2,
                                    ExerciseId = eId3,
                                    Exercise = new TestExerciseType1()
                                    {
                                            Id = eId3,
                                            Discriminator = "TestExerciseType1",
                                            Name = "E3",
                                            ToolType = new TestExerciseToolType()
                                            {
                                                    Id = 3,
                                                    Name = "M3"
                                            }
                                    }
                            },
                            new TestExerciseSequence()
                            {
                                    UnitId = uId2,
                                    ExerciseId = eId4,
                                    Exercise = new TestExerciseType1()
                                    {
                                            Id = eId4,
                                            Discriminator = "TestExerciseType1",
                                            Name = "E4",
                                            ToolType = new TestExerciseToolType()
                                            {
                                                    Id = 1, // Exception!
                                                    Name = "M1"
                                            }
                                    }
                            }
                    }
            };

            try
            {
                    var database = new TestDBHelper<TestLocalDBContext>();
                    database.AddUnit(u1);
                    database.AddUnit(u2); // Exception 
            }
            catch (Exception ex)
            {
                    Debug.WriteLine($"Error {ex.Message}");
            }
    }

提前感谢

EN

回答 1

Stack Overflow用户

发布于 2019-05-09 02:23:45

当使用实体时,上下文是实体的“所有者”。您可以“新建”一个实体,但您必须尊重上下文希望控制该实体。

当你做这样的事情时:

代码语言:javascript
复制
var parent1 = new Parent
{
   ParentId = 1,
   Child = new Child
   {
      ChildId = 1,
   },
}
var parent2 = new Parent
{
   ParentId = 2,
   Child = new Child
   {
      ChildId = 1,
   }
}
context.Parents.Add(parent1);
context.Parents.Add(parent2);
context.SaveChanges();

父级1将成功添加,但parent2将失败。它失败了,因为EF基本上会通过这两种情况,并将每个家长的孩子作为一个新的实体。当它遇到ID =1的父级1上的“新”子级时,就会出现PK冲突。

如果将实体设置为使用标识列,则可以避免此错误,但是父级1&2的子引用将指向两个具有不同生成ID的不同记录。

为了避免这种情况,在设置大容量数据时,首先创建和关联您的子实体,然后将它们关联到父实体。例如:

代码语言:javascript
复制
var child1 = new Child { ChildId = 1 };
var parent1 = new Parent 
{
  ParentId = 1,
  Child = child1
}
var parent2 = new Parent
{
  ParentId = 2,
  Child = child1
}
context.Parents.Add(parent1);
context.Parents.Add(parent2);
context.SaveChanges();

这样,父母双方都会引用相同的子元素,并且随着EF的持续,每个父服务器都将从上下文中解析相同的子引用。

对于一对多的关系也是如此:parent1.Children.Add(child1)而不是parent1.Children.Add(new Child { ChildId = 1 });

如果您想要创建实体并设置FK而不是引用,但是返回那些要作为普通实体使用的实体(必要时延迟加载引用),那么您应该使用DbSet.Create()而不是new

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/56048079

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档