#asp.net-mvc #entity-framework
Вопрос:
Каков наилучший способ хранения данных в базе данных при одновременной вставке нескольких классов в базу данных. У меня есть около 10 классов, где я хотел бы сохранить все данные вместе в базе данных.
Публикация некоторых классов с отношениями:
Public class ClassMain
{
public int ClassMainId { get; set; }
}
Public class ClassA
{
public int ClassAId { get; set; }
public int ClassMainId { get; set; }
public string ClassAName { get; set; }
}
Public class ClassB
{
public int ClassBId { get; set; }
public int ClassMainId { get; set; }
public string ClassBName { get; set; }
}
Как и выше, у меня есть несколько других таблиц с отношением
Я хотел бы вставить эти данные за один раз, если произойдет какой-либо сбой, я хотел бы откатить данные, в рамках Entity Framework или подхода к хранимым процедурам у нас есть подход для этого.
Я использую этот подход для достижения
https://dotnettutorials.net/lesson/unit-of-work-csharp-mvc/
Есть ли какой-то другой подход?
Ответ №1:
При условии, что они вставлены с использованием одного и того же DbContext и одного SaveChanges()
вызова, они будут зафиксированы в одной и той же транзакции.
Если сущности связаны друг с другом с помощью FKs, то лучшим решением, при котором вы хотите, чтобы БД управляла PKS и автоматически разрешала FKS, было бы настроить свойства навигации и добавить подчиненные сущности в объекты верхнего уровня, прежде чем добавлять объекты верхнего уровня в соответствующие наборы баз данных и вызывать изменения сохранения.
В вашем случае вы хотели бы что-то вроде:
public class ClassMain
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ClassMainId { get; set; }
[InverseProperty("ClassMain")]
public virtual ICollection<ClassA> ClassAs { get; set;} = new List<ClassA>();
[InverseProperty("ClassMain")]
public virtual ICollection<ClassB> ClassBs { get; set;} = new List<ClassB>();
}
public class ClassA
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ClassAId { get; set; }
public int ClassMainId { get; set; }
[ForeignKey("ClassMainId")]
public virtual ClassMain ClassMain{ get; set; }
public string ClassAName { get; set; }
}
Public class ClassB
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ClassBId { get; set; }
public int ClassMainId { get; set; }
[ForeignKey("ClassMainId")]
public virtual ClassMain ClassMain{ get; set; }
public string ClassBName { get; set; }
}
С EF Core 5 идентификатор класса FK в классах A и B является необязательным. Ядро EF может просто использовать свойства тени для отображения, а не отображать их как свойства. (Рекомендуется)
Теперь, когда вы переходите к созданию домена классов и связанных с ним A и B, вы можете просто создать новые экземпляры, заполнив различные поля, и добавить объект верхнего уровня в набор баз данных.
using(var context = new AppDbContext())
{
var newMain = new ClassMain
{
// ... set main properties, but don't worry about PK
ClassA = new ClassA { ClassAName = someAName },
ClassB = new ClassB { ClassBName = someBName }
};
context.ClassMains.Add(newMain);
context.SaveChanges();
}
Вы также можете сделать это без идентификаторов PKS, где вы можете задать PKs/FKs в коде, хотя вы несете ответственность за то, чтобы они были уникальными. Настроив свойства навигации, EF позаботится о связывании FKS, когда все будет сохранено. Они будут экономить все вместе или вообще ничего не будут экономить.
Правка: Если вы не хотите, чтобы ClassMain содержал коллекции для экземпляров ClassA и ClassB, вы можете удалить эти коллекции и атрибуты обратных свойств. Заполнение экземпляров будет больше похоже на:
using(var context = new AppDbContext())
{
var newMain = new ClassMain();
var classA = new ClassA { ClassAName = someAName, ClassMain = newMain };
var classB = new ClassB { ClassBName = someBName, ClassMain = newMain };
context.ClassAs.Add(classA);
context.ClassBs.Add(classB);
context.SaveChanges();
}
В этом случае экземпляр ClassMain будет сохранен вместе с классами A и B.
Комментарии:
1. Необходимо ли создавать это
public class ClassMain { [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ClassMainId { get; set; } [InverseProperty("ClassMain")] public virtual ICollection<ClassA> ClassAs { get; set;} = new List<ClassA>(); [InverseProperty("ClassMain")] public virtual ICollection<ClassB> ClassBs { get; set;} = new List<ClassB>(); }
2. Если вы хотите, чтобы только ClassA и ClassB ссылались на домен классов, чтобы он не содержал список каждого из них, это, безусловно, возможно. Просто создайте ClassMain, ClassA и ClassB, установите ссылку ClassMain в каждом из них на один экземпляр ClassMain, добавьте экземпляры ClassA и B в соответствующие наборы баз данных и вызовите
SaveChanges()
. Я расширил ответ, чтобы обрисовать этот сценарий.
Ответ №2:
Если у вас есть связи между таблицами, решение, которое сказал @Steve Py, является лучшим. Но если у вас нет никаких отношений между сущностями, вы можете использовать явные транзакции.
using var context = new AppDbContext();
using var transaction = context.Database.BeginTransaction();
var newMain = new ClassMain();
context.ClassMains.Add(newMain);
context.SaveChanges();
var newClassA = new ClassA
{
ClassMainId = newMain.ClassMainId
};
context.ClassAs.Add(newClassA);
var newClassB = new ClassB
{
ClassMainId = newMain.ClassMainId
};
context.ClassBs.Add(newClassB);
context.SaveChanges();
transaction.Commit();