[linq2db] 用 LINQ 操作 DB2 iSeries AS400

以往操作 AS400 首選用 Dapper 控制,在把玩 linq2db 時無意間發現有好心人實作了 AS400 的 Provider,立馬來用用看

本文連結 

開發環境

VS 2017 15.9.5

.NET Framework 4.7.2

安裝 IBM i Access Client Solutions , V7R1 Service Pack SI64724,這是 AS400 的執行環境,裡面會有 IBM.Data.DB2.iSeries.dll

 

前置條件

安裝套件

Install-Package linq2db4iSeries

https://github.com/LinqToDB4iSeries/Linq2DB4iSeries

修改組態

裝完之後會在 app.config/web.config 加入以下內容,這裡需要調整一下,0.0.0.0-2.6.4.0 改為 2.6.0.0

  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="linq2db" publicKeyToken="e41013125f9e410a" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-2.6.4.0" newVersion="2.6.4.4" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>

 

然後,加入以下組態設定

  <configSections>
    <section name="linq2db" type="LinqToDB.Configuration.LinqToDBSection, linq2db" requirePermission="false" />
  </configSections>
  <linq2db>
    <dataProviders>
      <add name="iSeriesProvider" 
           type="LinqToDB.DataProvider.DB2iSeries.DB2iSeriesFactory, LinqToDB.DataProvider.DB2iSeries" 
	   default="true" />
    </dataProviders>
  </linq2db>

 

別忘了連線字串

  <connectionStrings>
    <add name="MemberDb"
         connectionString="Data Source= your server;User ID=your id;Password=your password;Default Collection=YAO01" 
         providerName="iSeriesProvider" />
 
  </connectionStrings>

 

建立AS400資料表

DROP  YAO01.MEMBER
CREATE TABLE YAO01.MEMBER
(
   ID      INTEGER        NOT NULL,
   NAME    VARCHAR(100)   NOT NULL,
   AGE     INTEGER        NOT NULL,
   REMARK  VARCHAR(300)
);
ALTER TABLE YAO01.MEMBER
   ADD CONSTRAINT MEMBER_PK
   PRIMARY KEY (ID);

COMMIT;

 

DROP  YAO01.IDENTITY
CREATE TABLE YAO01.IDENTITY
(
   MEMBER_ID  INTEGER        NOT NULL,
   ACCOUNT    VARCHAR(100)   NOT NULL,
   PASSWORD   VARCHAR(100)   NOT NULL,
   REMARK     VARCHAR(300)      NOT NULL
);
ALTER TABLE YAO01.IDENTITY
   ADD CONSTRAINT IDENTITY_PK
   PRIMARY KEY (MEMBER_ID);
COMMIT;

 

建立 POCO

因為還沒有找到相關的工具可以幫助產生 POCO,所以得手動建立,我定義了兩個類別,這兩個類別的關係是一對一

/// <summary>
/// </summary>
[Table("MEMBER")]
public class Member
{
    /// <summary>
    ///     ID
    /// </summary>
    [PrimaryKey]
    [Column]
    public int ID { get; set; }
 
    /// <summary>
    ///     NAME
    /// </summary>
    [Column]
    public string NAME { get; set; }
 
    /// <summary>
    ///     AGE
    /// </summary>
    [Column]
    public int AGE { get; set; }
 
    /// <summary>
    ///     REMARK
    /// </summary>
    [Column]
    [Nullable]
    public string REMARK { get; set; }
 
    [Association(ThisKey = "ID", OtherKey = "MEMBER_ID", CanBeNull = true)]
    public Identity IDENTITY { get; set; }
}

 

/// <summary>
/// </summary>
[Table("IDENTITY")]
public class Identity
{
    /// <summary>
    ///     MEMBER_ID
    /// </summary>
    [Column]
    [PrimaryKey]
    public int MEMBER_ID { get; set; }
 
    /// <summary>
    ///     ACCOUNT
    /// </summary>
    [Column]
    public string ACCOUNT { get; set; }
 
    /// <summary>
    ///     PASSWORD
    /// </summary>
    [Column]
    public string PASSWORD { get; set; }
 
    /// <summary>
    ///     REMARK
    /// </summary>
    [Column]
    [Nullable]
    public string REMARK { get; set; }
 
    [Association(ThisKey = "MEMBER_ID", OtherKey = "ID", CanBeNull = false)]
    public Member MEMBER { get; set; }
}

 

這裡跟 EF 很像,要有一個連線物件來管理資料表

public class MemberDb : DataConnection {     public MemberDb()         : base("MemberDb")     {     }     public ITable<Member> Members     {         get { return this.GetTable<Member>(); }     }     public ITable<Identity> Identities     {         get { return this.GetTable<Identity>(); }     } }

 

還原資料表

在測試開始之前,結束之後復原 DB,我這裡使用 REMARK 欄位來當成過濾條件

[TestInitialize]
public void Before()
{
    TestHook.Delete();
}
 
[TestCleanup]
public void After()
{
    TestHook.Delete();
}

 

只要是塞 TestData 的內容我就把他幹掉

[TestClass]
public class TestHook
{
    public static readonly string TestData = "出發吧,跟我一起進入偉大的航道";
 
    public static void Delete()
    {
        using (var db = new MemberDb())
        {
            db.BeginTransaction();
            try
            {
                db.Members.Where(p => p.REMARK == TestData).Delete();
                db.Identities.Where(p => p.REMARK == TestData).Delete();
 
                db.CommitTransaction();
            }
            catch (Exception e)
            {
                Console.WriteLine(db.LastQuery);
                Console.WriteLine(e);
                db.RollbackTransaction();
                throw;
            }
        }
    }
}

原本是打算組合 Delete 命令一次刪多個資料表,但是一直不成功,只好一筆一筆刪

實作

  • 我的驗證方式只是判斷資料是不是異動成功,記得到了真正開發的時候,要根據你的情境驗證欄位資料
  • 使用 db.LastQuery 觀察渲染出來的語法

新增

[TestMethod]
public void 新增()
{
    var memberToDb = new Member
    {
        ID = 1,
        NAME = "yao",
        AGE = 20,
        REMARK = TestHook.TestData
    };
    using (var db = new MemberDb())
    {
        var insertCount = db.Insert(memberToDb);
        Console.WriteLine(db.LastQuery);
        Assert.AreEqual(1, insertCount);
    }
}

 

修改

以下的案例都要先插一筆我可以預期的資料,測完之後,刪除他

[TestMethod]
public void 修改一整筆()
{
    var fromDb = this.Insert();
    var updateDb = new Member
    {
        ID = fromDb.ID,
        NAME = "yao1",
        REMARK = TestHook.TestData
    };
    using (var db = new MemberDb())
    {
        var updateCount = db.Update(updateDb);
        Console.WriteLine(db.LastQuery);
        Assert.AreEqual(1, updateCount);
    }
}

 

[TestMethod]
public void 修改部分()
{
    var fromDb = this.Insert();
    var name = "yao2";
    using (var db = new MemberDb())
    {
        var updateCount = db.Members
                            .Where(p => p.ID == fromDb.ID)
                            .Set(p => p.NAME, name)
                            .Update();
        Console.WriteLine(db.LastQuery);
        Assert.AreEqual(1, updateCount);
    }
}

 

Join

查詢的案例兩張表各別新增 10 筆,才進入查詢

  • 能在 Select 使用導覽屬性是因為有在兩張表定義關聯
  • 尾巴 Join 頭,渲染的語法是用 InnerJoin,尾巴有資料頭一定有
  • 不想用導覽的方式,也可以自己寫 Join
[TestMethod]
public void 查詢IDENTITY_InnerJoin()
{
    this.Inserts();
 
    using (var db = new MemberDb())
    {
        var filter = db.Identities
                       .Select(p => new
                       {
                           p.MEMBER.ID,
                           p.MEMBER.AGE,
                           p.MEMBER.NAME,
                           p.PASSWORD,
                           p.ACCOUNT
                       })
                       .Where(p => p.ID > 0)
                       .ToList()
            ;
        Console.WriteLine(db.LastQuery);
    }
}

 

頭 Join 尾巴,渲染的語法是用 OuterJoin,頭有資料,尾巴不一定有

[TestMethod]
public void 查詢MEMBER_OuterJoin()
{
    this.Inserts();
 
    using (var db = new MemberDb())
    {
        var filter = db.Members
                       .Select(p => new
                       {
                           p.ID,
                           p.AGE,
                           p.NAME,
                           p.IDENTITY.PASSWORD,
                           p.IDENTITY.ACCOUNT
                       })
                       .Where(p => p.ID > 0)
                       .ToList()
            ;
        Console.WriteLine(db.LastQuery);
    }
}

 

交易

linq2db 也有實作自己的交易,使用起來也是相當容易

這看起來很簡單的功能,但我今天第一次成功

[TestMethod]
public void 交易()
{
    this.Inserts();
    var memberToDb = new Member
    {
        ID = 1,
        NAME = "yao",
        AGE = 20,
        REMARK = TestHook.TestData
    };
    using (var db = new MemberDb())
    {
        db.BeginTransaction();
        try
        {
            db.Insert(memberToDb);
            throw new Exception();

            db.CommitTransaction();
        }
        catch (Exception e)
        {
            db.RollbackTransaction();
        }
    }
}

 

要能使用交易需要有 log(journal),當使用CREATE SCHEMA 會有 log

上圖出自

https://github.com/LinqToDB4iSeries/Linq2DB4iSeries

 

專案位置

https://github.com/yaochangyu/sample.dotblog/tree/master/ORM/Linq2Db/Lab.As400.CRUD1
 

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo