如果資料庫都隸屬於同一台伺服器,資料庫之間要 JOIN 彼此的資料表是相對容易的,在有權限的前提下,只要在資料表的前面加上「[資料庫名稱].[結構描述]
」,就能存取得到其他資料庫的資料表,就像這樣:[DB].[dbo].[Table]
。
但是在 Entity Framework Core 中,似乎沒有那麼容易,通常一個資料庫會建成一個 DbContext,當我們直接把隸屬於不同 DbContext 的 DbSet 拿來做 Join,我們得到以下的錯誤訊息:
Cannot use multiple context instances within a single query execution. Ensure the query uses a single context instance.
實驗情境
我們建立了兩個資料表:Member
及 Order
,分別各自隸屬於不同的資料庫:Test
及 Test2
,利用 EF Core Power Tools 我們建立了兩個 DbContext,分別是:TestContext
及 Test2Context
。
我們直接用 EF Core 做 GroupJoin
,沒意外的話應該會噴例外錯誤。
var testConnStr = "...";
var test2ConnStr = "...";
var testCtxOpt = new DbContextOptionsBuilder<TestContext>().UseSqlServer(testConnStr).Options;
var test2CtxOpt = new DbContextOptionsBuilder<Test2Context>().UseSqlServer(test2ConnStr).Options;
var testCtx = new TestContext(testCtxOpt);
var test2Ctx = new Test2Context(test2CtxOpt);
// 此段程式碼會發生錯誤
var membersWithOrders = await testCtx.Members.GroupJoin(
test2Ctx.Orders,
m => m.Id,
o => o.MemberId,
(m, olist) => new { Member = m, Orders = olist })
.ToListAsync();
錯誤訊息說的是 Entity Framework Core 的一個硬傷,無法在單一查詢中使用多個 DbContext,所以其中一類的解法是把單一查詢拆開成多個查詢,取出資料之後,在應用程式中進行聯結運算,這類解法要考慮的是取出資料量的大小,單一查詢拆開成多個查詢,有可能會有資料冗餘,造成「弱水三千,只取一瓢飲。
」的現象,這類的解法不是我要的。
另一類的解法,就讓單一查詢維持單一 DbContext,但是可以跨資料庫存取,目前研究出來一種方式,提供給各位朋友參考,如果大家有不一樣的解決方式,還請不吝與我分享。
解法:在同一個 DbContext 建立來自其他資料庫的 DbSet
幸好 EF Core Power Tools 建出來的 DbContext 類別,是 partial class
,我們可以自行擴充 TestContext,新增一個 OrdersOfTest2
的 DbSet。
public partial class TestContext
{
public DbSet<Order> OrdersOfTest2 { get; set; }
}
這樣就符合 Entity Framework Core 的規則「單一查詢、單一 DbContext。」,但是,想也知道,如果就這麼直接拿來操作,一定是不行的,直接就噴錯了。
Invalid column name 'XXX'.
不過,這就不是 Entity Framework Core 的錯誤了,這個是 SQL Server 的錯誤,因為在這個資料庫中沒有 Order 這個資料表,自然也就沒有 Order 相關的欄位,有錯誤是正常。
接著,我們要告訴 Entity Framework Core,JOIN 的 Order 應該來自哪裡?我們使用 FromSql 直接指定 Order 的查詢語句,然後原本 GroupJoin 的目標改為 ordersInTestCtx
,這樣應該就可以取得到資料了。
var ordersInTestCtx = testCtx.OrdersOfTest2.FromSql($"SELECT * FROM [Test2].[dbo].[Order]");
var membersWithOrders = await testCtx.Members.GroupJoin(
ordersInTestCtx,
m => m.Id,
o => o.MemberId,
(m, olist) => new { Member = m, Orders = olist })
.ToListAsync();
從 Entity Framework Core 產生的查詢語句中,可以看到,我們指定的 Order 查詢語句被包成了子查詢。
到目前,我也只研究出這一種方法,曾經嘗試過用「檢視(View)
」,但是 View 沒有主索引鍵,GroupJoin 會有問題,歡迎大家分享更好的解法。