使用 Entity Framework 添加和更新实体
Posted
技术标签:
【中文标题】使用 Entity Framework 添加和更新实体【英文标题】:Adding and updating entities with Entity Framework 【发布时间】:2013-07-21 08:10:39 【问题描述】:在我的上一个项目中,我使用了 Entity Framework 5 Code First。我完成了我的项目,但在开发过程中遇到了很多痛苦。
我试图在下面解释我的痛苦:
我的数据访问逻辑层中有几个数据类,例如 Product、ProductCategory、Order、Company、Manufacturer 等……每个类都有一些对其他类的引用。例如,Product 实例具有 ProductCategory 属性。
在我的数据访问对象类的 Add 和 Update 方法中,我在上下文中将每个属性(原始类型除外)的状态设置为 Unchanged 或 Modified。下面是一些 dao 类的更新方法的一部分:
context.Entry(entity).State = System.Data.EntityState.Modified;
if (entity.Category != null)
context.Entry(entity.Category).State = System.Data.EntityState.Unchanged;
if (entity.Manufacturer != null)
context.Entry(entity.Manufacturer).State = System.Data.EntityState.Unchanged;
foreach (var specificationDefinition in entity.SpecificationDefinitions)
context.Entry(specificationDefinition).State = System.Data.EntityState.Unchanged;
foreach (var specificationValue in specificationDefinition.Values)
context.Entry(specificationValue).State = System.Data.EntityState.Unchanged;
这段代码是这样的。我的一些添加或更新方法大约有 30 行代码。我认为我做错了,添加或更新实体不应该这么痛苦,但是当我不设置对象的状态时,我要么得到异常,要么在数据库中出现重复条目。我真的必须设置映射到数据库的每个属性的状态吗?
【问题讨论】:
您可以添加创建数据的代码吗?如果您在类中的引用是正确的,EF 应该只创建一次所有实例。 是的,它一次创建所有实例。这实际上就是问题所在。让我再解释一下问题。假设我有一个 Product 实例,其中有一个 ProductType 实例。假设 Product 实例在过去已经持久化。当我尝试更新该 Product 实例时,如果我没有将 ProductType 属性的状态设置为 Unchanged,EF 会创建一个重复的 ProductType 实例(这是我不想要的情况)。 【参考方案1】:您可以通过以下方式替换您的代码:
context.Products.Attach(entity);
context.Entry(entity).State = System.Data.EntityState.Modified;
这是相同的原因(除非相关实体已经以不同于Unchanged
的状态附加到上下文)是Attach
放置entity
包括所有相关实体在对象图中进入状态Unchanged
的上下文。之后为entity
将状态设置为Modified
只会将产品(而非相关实体)的状态从Unchanged
更改为Modified
。
【讨论】:
如果实体是从另一个(尚未处理的)上下文中检索到的,这将不起作用。你会得到一个例外。 @MichaelLogutov:你是对的,但我几乎可以肯定问题中的场景是一个独立的场景(即早期的上下文已经被处理)。否则无论如何都会容易得多,因为您通常根本不必摆弄任何实体状态,因为更改跟踪会自动跟踪正确的状态。【参考方案2】:好吧,那你只是做错了。 除了我的评论之外,我还为您创建了一个示例,显示 EF 默认情况下不会创建重复项。
我有两个班级:
public class Product
public int Id get; set;
public string Name get; set;
public ProductCategory Category get; set;
public decimal Price get; set;
public class ProductCategory
public int Id get; set;
public string Name get; set;
一个上下文:
public class MyContext : DbContext
public DbSet<Product> Products get; set;
public DbSet<ProductCategory> ProductCategories get; set;
public MyContext()
: base("name=MyContext")
public MyContext(string nameOrConnectionString)
: base(nameOrConnectionString)
protected override void OnModelCreating(DbModelBuilder modelBuilder)
Database.SetInitializer<MyContext>(null);
// Table mappings
modelBuilder.Entity<Product>().ToTable("Product");
modelBuilder.Entity<ProductCategory>().ToTable("ProductCategory");
base.OnModelCreating(modelBuilder);
然后是一个初始化类(如果需要,可以从其他策略继承):
public class InitDb<TContext> : DropCreateDatabaseAlways<TContext>
where TContext : DbContext
主程序:
static void Main(string[] args)
var prodCat = new ProductCategory()
Name = "Category 1"
;
var prod = new Product()
Name = "Product 1",
Category = prodCat,
Price = 19.95M
;
using (var context = new MyContext())
var initializer = new InitDb<MyContext>();
initializer.InitializeDatabase(context);
Console.WriteLine("Adding products and categories to context.");
context.ProductCategories.Add(prodCat);
context.Products.Add(prod);
Console.WriteLine();
Console.WriteLine("Saving initial context.");
context.SaveChanges();
Console.WriteLine("Context saved.");
Console.WriteLine();
Console.WriteLine("Changing product details.");
var initProd = context.Products.Include(x => x.Category).SingleOrDefault(x => x.Id == 1);
PrintProduct(initProd);
initProd.Name = "Product 1 modified";
initProd.Price = 29.95M;
initProd.Category.Name = "Category 1 modified";
PrintProduct(initProd);
Console.WriteLine();
Console.WriteLine("Saving modified context.");
context.SaveChanges();
Console.WriteLine("Context saved.");
Console.WriteLine();
Console.WriteLine("Getting modified product from database.");
var modProd = context.Products.Include(x => x.Category).SingleOrDefault(x => x.Id == 1);
PrintProduct(modProd);
Console.WriteLine();
Console.WriteLine("Finished!");
Console.ReadKey();
static void PrintProduct(Product prod)
Console.WriteLine(new string('-', 10));
Console.WriteLine("Id : 0", prod.Id);
Console.WriteLine("Name : 0", prod.Name);
Console.WriteLine("Price : 0", prod.Price);
Console.WriteLine("CatId : 0", prod.Category.Id);
Console.WriteLine("CatName : 0", prod.Category.Name);
Console.WriteLine(new string('-', 10));
这会导致以下控制台输出:
Adding products and categories to context.
Saving initial context.
Context saved.
Changing product details.
----------
Id : 1
Name : Product 1
Price : 19,95
CatId : 1
CatName : Category 1
----------
----------
Id : 1
Name : Product 1 modified
Price : 29,95
CatId : 1
CatName : Category 1 modified
----------
Saving modified context.
Context saved.
Getting modified product from database.
----------
Id : 1
Name : Product 1 modified
Price : 29,95
CatId : 1
CatName : Category 1 modified
----------
Finished!
此外,在 SQL Server Management Studio 中查看时,此解决方案仅创建(和更新)了一个产品和一个类别。
当然,您应该使用存储库来检索、更新和删除您的数据和工作单元。这些已被排除在示例之外。
因此,如果您不发布任何代码,我们将无法为您提供更多帮助 :-)
【讨论】:
以上是关于使用 Entity Framework 添加和更新实体的主要内容,如果未能解决你的问题,请参考以下文章
向数据库添加新表后如何更新 Entity Framework Core
在 MVC 中使用 Entity Framework Code First 更新现有数据库
无法更新 Entity Framework Core 中的标识列
向模型 (edmx) 添加新表而不更新 Database First Entity Framework MVC 应用程序中的其他模型