我有一个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”
因此,问题是:为前端设计模型的最简单方法是什么,这样我就只能插入后端生成的单元列表。
下面是示例代码
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();
}
}
}
}和测试代码
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}");
}
}提前感谢
发布于 2019-05-09 02:23:45
当使用实体时,上下文是实体的“所有者”。您可以“新建”一个实体,但您必须尊重上下文希望控制该实体。
当你做这样的事情时:
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的不同记录。
为了避免这种情况,在设置大容量数据时,首先创建和关联您的子实体,然后将它们关联到父实体。例如:
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。
https://stackoverflow.com/questions/56048079
复制相似问题