2010年4月28日 星期三

Entity Framework: 新增關聯資料的方法

使用 Entity Framework 新增一筆資料是極其簡單的事。然而建立相關聯的資料呢?也相當簡單!以下介紹常見的狀況。

DataSchema

在這個範例中,使用最簡單的 master-detail 的資料表。一個是 Order,一個是 OrderDetail。Sql Script 如下

CREATE TABLE [dbo].[Order](
    [OrderId] [int] IDENTITY(1,1) NOT NULL,
    [OrderDate] [datetime] NOT NULL,
 CONSTRAINT [PK_Order] PRIMARY KEY CLUSTERED 
(
    [OrderId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON)
)
go
CREATE TABLE [dbo].[OrderDetail](
    [DetailId] [int] IDENTITY(1,1) NOT NULL,
    [OrderId] [int] NOT NULL,
    [ProductName] [nchar](50) COLLATE Chinese_Taiwan_Stroke_CI_AS NOT NULL,
 CONSTRAINT [PK_OrderDetail] PRIMARY KEY CLUSTERED 
(
    [DetailId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON)
)
go
ALTER TABLE [dbo].[OrderDetail]  WITH CHECK ADD  CONSTRAINT [FK_OrderDetail_Order] FOREIGN KEY([OrderId])
REFERENCES [dbo].[Order] ([OrderId])
GO
ALTER TABLE [dbo].[OrderDetail] CHECK CONSTRAINT [FK_OrderDetail_Order]
GO

使用EDM designer 產出的 Model 如下圖image

範例一

以下是最簡單的方法,也是基本上我認為這樣的寫法最適當

class Program
{
  static void Main(string[] args)
  {
    AddData1();
    ShowData();
  }

  private static void ShowData()
  {
    using (Database1Entities context = new Database1Entities())
    {
      var orders = context.Orders.Include("OrderDetails");
      foreach (var order in orders)
      {
        Console.WriteLine("order id = {0}", order.OrderId);
        foreach (var d in order.OrderDetails)
          Console.WriteLine("detail prod={0}", d.ProductName);
      }
    }
  }

  private static void AddData1()
  {
    using (Database1Entities context = new Database1Entities())
    {
      var order = new Order() {
        OrderDate = new DateTime(2010, 4, 12)
      };

      var detail = new OrderDetail {
        ProductName = "Prod1"
      };
      order.OrderDetails.Add(detail);
      context.Orders.AddObject(order);
      context.SaveChanges();
    }
  }
}

要注意的是,我們使用了 order.OrderDetails.Add(detail) 將order 與 detail 的關聯建立起來。接下來使用 context.Orders.AddObject(order) 將 order 新增到 context 中,因此當 context.SaveChanges() 時,context 會查覺到 order 是新增的資料,才會將資料寫到資料庫中。

這樣的方法相當完美,原因在開發人員只需關注物件模型的關係即可,不必知道物件模型關聯的 key 是哪一個 property。

範例二

下面的方法也可以執行。

private static void AddData2()
{
  using (Database1Entities context = new Database1Entities())
  {
    var order = new Order()
    {
      OrderDate = new DateTime(2010, 4, 12)
    };
    context.Orders.AddObject(order);
    context.SaveChanges();

    var detail = new OrderDetail
    {
      OrderId = order.OrderId,
      ProductName = "Prod1"
    };
    context.OrderDetails.AddObject(detail);
    context.SaveChanges();
  }
}

與範例一不同的是,範例二並不強調 order 與 detail 的物件關聯,而是直接指定 detail 的 OrderId。因此order 與 detail 是分開寫入資料庫的。

範例二就是比較差的寫法了,原因是在開發人員需要了解關聯的property 為何,此例為 OrderDetai 的 OrderId 關聯到 Order 的 OrderId。長久下來,開發人員會習慣以 Data driven 的寫作方式看待 Entity Framework,故不建議這樣做。

範例三

範例三與範例二大致相同

private static void AddData3()
{
  using (Database1Entities context = new Database1Entities())
  {
    var order = new Order()
    {
      OrderDate = new DateTime(2010, 4, 12)
    };
    context.Orders.AddObject(order);
    context.SaveChanges();

    var detail = new OrderDetail
    {
      Order = order, //這一行換了
      ProductName = "Prod1"
    };
    context.OrderDetails.AddObject(detail);
    context.SaveChanges();
  }
}

範例三與範例二的概念相同。原來範例二中 detail 是指定 OrderId ,而範例三中 detail 改指定 Order 為 order

只增加 Detail

如果已經知道 master 的 primary key,只想新增 detail 的資料呢?下面的範例四是可以執行的。

範例四

static void Main(string[] args)
{
  AddData1();
  AddNewDetail1();
  ShowData();
}

private static void AddNewDetail1()
{
  using (Database1Entities context = new Database1Entities())
  {
    var order = context.Orders.Where(o => o.OrderId == 1).First();
    var detail = new OrderDetail
    {
      ProductName = "Prod2"
    };
    order.OrderDetails.Add(detail);
    context.SaveChanges();
  }
}

因為物件模型的關係,範例四的做法相當直覺,也相當多的開發人員這樣寫。但是,我們已經知道了 OrderId 為1,範例四會先載入 OrderId 為1的資料並載入記憶體,再進行後續的新增detail 動作。這樣一來,反而效能不彰。可不可以像以前一樣,直接新增一筆 OrderDetail 就好了,不需要載入 Order 啊?

下面的範例五可以在不載入 master (即 order) 到記憶體的情況下增加 detail 資料。

範例五

static void Main(string[] args)
{
  AddData1();
  AddNewDetail();
  ShowData();
}

private static void AddNewDetail()
{
  using (Database1Entities context = new Database1Entities())
  {
    var detail = new OrderDetail
    {
      OrderId = 1, //指定了 master 的 key
      ProductName = "Prod2"
    };
    context.OrderDetails.AddObject(detail);
    context.SaveChanges();
  }
}

結論

在 Entity Framework 中,關聯資料的寫法可以有相當多種,但其中的差別需要開發人員注意哦!

6 則留言:

--哲-- 提到...

hi..大大你好
請問這邊範例
是EF1還是EF4的Code阿?

秉程 提到...

我使用的是 EF 4

--哲-- 提到...

瞭解
想說EF1應該Gen不出OrderDetail.OrderId這個屬性才對
EF4有這種特性真的滿方便的^^

Phil 提到...

在研究 MS Nerd Dinner. 不太清楚Add與AddObject的差異. 看了您的文章就懂了. 感恩啦!

Phil Chen 提到...

謝版主. 小的剛從Fat client轉到asp.net.小的認為EF好像是特地為MVC打造的. 像這一行
var orders = context.Orders.Include("OrderDetails");
直接由controller拋orders到view. 資料就跑出來了.

Vasili 提到...

您好!

請問一下,
如果我開好的 Entity 當中,
OrderDetail 沒有 OrderId的屬性,
是否開錯了呢?!

Share with Facebook