У меня есть база данных SQL Server в качестве серверной части веб-службы и локальная база данных SQLite на мобильном интерфейсе.
Обе используют EF Core 2.2.4 с подходом TpH и разделяют большинствоклассов C #, но - конечно - у внутренних классов есть больше свойств из-за административных вещей, о которых нужно заботиться.Для моделирования различий используются условные атрибуты и свободный API (см. Код ниже).
Основная идея заключается в том, что содержимое базы данных создается на бэкэнде, а выдержка затем сохраняется в базе данных внешнего интерфейса.
В самом конце (и в упрощенном виде) локальная база данных должна хранить только список единиц.
Каждая единица содержит список упражнений (n: m).
Упражнения получены из абстрактного класса, и некоторые типы упражнений могут содержать список необходимых инструментов (1: n), которые хранятся в дополнительной таблице.
Каждое упражнение уникально, так что я могу использовать егопервичный ключ для отношения юнит-упражнение n: m.
С другой стороны, на инструмент может ссылаться более одного упражнения, что вызывает проблемы при добавлении юнитов с упражнениями в локальную базу данных внешнего интерфейса.
Проблема состоит в том, что в упражнении 1: n для отображения инструментов также используется первичный ключ инструмента, потому что именно так его создает бэкэнд, и, следовательно, если первичный ключ инструмента во внешнем интерфейсе уже существует,frontend db Add()
вызов завершается неудачно с
Ошибка SQLite 19: «Не удалось выполнить ограничение UNIQUE: Tool.Id»
Итак, вопрос в том, что является самым простым способом.(пере) проектировать моdel для внешнего интерфейса, так что я только вставляю список единиц, сгенерированных бэкэндом
- , не получая ошибку
- , не делая много вещей из базы данных вручную, потому что это то, чтоEF должен сделать для меня
Вот пример кода
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}");
}
}
Заранее спасибо