• DBEngine và Workspace trong việc truyền tải dữ liệu giữa hai Database
  • DBEngine và Workspace trong việc truyền tải dữ liệu giữa hai Database

    Xuân Thanh > 08-08-18, 04:38 PM

    1/ Lời dẫn
    Chào các bạn!
    Trên Forum cũng có nhiều bài viết về DAO. Hôm nay xin gửi tới các bạn một phương thức mới(mà cũ vì cái này tôi đã áp dụng cách nay khoảng 20 năm về trước khi còn dùng Access 2.0 và Access 97). Đó là sử dụng DBEngine và Workspace. Vậy DBEngine và Workspace là gì?
    Đối tượng DBEngine là một thuộc tính của đối tượng Application Access và đại diện cho đối tượng cấp cao nhất trong mô hình DAO. Đối tượng DBEngine chứa tất cả các đối tượng khác trong phân cấp đối tượng DAO, nhưng không giống như nhiều đối tượng DAO khác, bạn không thể tạo các đối tượng DBEngine bổ sung
    Workspace là một không gian làm việc của DB mà ở đó ta có thể tạo ra nhiều không gian làm việc để có thể làm việc cùng một lúc.Ta có thể hiểu nôm na là DBEngine giông như một ngôi nhà và ỏ trong đó có nhiều phòng(Workspace). Ta có thể đưa các vật dụng(dữ liệu) vào nhiều phòng một lúc và cũng có thê di chuyển đồ dùng từ phòng này qua phòng khác mà không sợ đụng chạm giữa các đồ vật
    Tôi sẽ trở lại với Workspace trong một chủ đề khác


    2/ Nói về DeMo
    Trong DeMo này vừa nhăm giới thiệu về DBEngine và Workspace đồng thời vừa trả lời bạn NguyenDungAnh hỏi về khóa sổ không cho cập nhật dữ liêu sau khi khóa sổ. Link bài viết ở đây http://thuthuataccess.com/forum/thread-10612.html
    Trong DeMo tôi giả lập một dự án Sổ Quỹ đơn giản, chỉ có các Table, Form và Modul cần thiết. Các bạn tải DeMo đính kèm và mở các Table, Form và Modul để xem
    Chú ý : Các bạn tải DeMo đính kèm và giải nén vào ổ đĩa cứng. File LuuDuLieu các bạn có thể để chung thư mục với file DeMo hoặc khác thư mục. Trước khi vận hành thử, các bạn chỉnh lại đường dẫn đên file LuuDuLieu theo đúng trên máy của các bạn. Khóa sổ được thực hiện qua frmKhoaSo và không cho cập nhật thêm được thực hiện qua txtNgay.AfterUpdate() của frmPhieuThuChi


    Mong các bạn cùng trao đổi thêm
    Thân mến
    Xuân Thanh
  • RE: DBEngine và Workspace trong việc truyền tải dữ liệu giữa hai Database

    NguyenDungAnh > 08-08-18, 10:22 PM

    Cám ơn bác đã chia xẻ kinh nghiệm, em có nhập thử data vào mấy bảng đó nhưng khi bấm khóa sổ thì mở mục lưu dữ liệu vẫn trắng, em dùng office 2013 64 bit
  • RE: DBEngine và Workspace trong việc truyền tải dữ liệu giữa hai Database

    Xuân Thanh > 09-08-18, 12:20 PM

    (08-08-18, 10:22 PM)NguyenDungAnh Đã viết: Cám ơn bác đã chia xẻ kinh nghiệm, em có nhập thử data vào mấy bảng đó nhưng khi bấm khóa sổ thì mở mục lưu dữ liệu vẫn trắng, em dùng office 2013 64 bit

    1/ Đã chỉnh lại đương dẫn trong hàm CopyDLDaTa chưa?
    2/ Tôi không có Office 2013 nên không test được. Có thể là ở 64bit

    Bạn thử thế này xem nhé. Mở một modul trống, gõ DBEngine rồi ghi dấu (.) nếu nó xòe ra một danh sách cho bạn chọn Workspace thì mọi chuyện vẫn ổn và kiểm tra ở chỗ khác

    Tôi làm trên Office2010 32bit ok mà
    Thân mến
  • RE: DBEngine và Workspace trong việc truyền tải dữ liệu giữa hai Database

    NguyenDungAnh > 09-08-18, 06:06 PM

    ok bác để em kiểm tra xem
  • RE: DBEngine và Workspace trong việc truyền tải dữ liệu giữa hai Database

    ongke0711 > 09-08-18, 09:25 PM

    Trước đây cũng có một bài của maidinhdan cũng gần với cái cách dùng DBEngine về việc Append dữ liệu giữa 2 file Access khác nhau. Các bạn cũng có thể tham khảo thêm.
    Link: http://thuthuataccess.com/forum/thread-9572.html

    Tôi cũng đóng góp 1 cách khác:
    - Không cần khai báo Database cần backup cho DBEngine Collection.
    - Dùng câu lệnh SQL để Insert dữ liệu lên table đích. Vì câu lệnh SQL luôn xử lý nhanh hơn DAO Recordset. "Insert Into..." nó cập nhật một lúc nguyên một bó records (batch update), còn đối với DAO Recordset thì nó Loop qua từng record để update.
      
        INSERT INTO tên Table IN 'đường dẫn file database đích để lưu' SELECT * FROM Table WHERE ...

    - Vì dùng câu lệnh SQL nên biến ngày/tháng phải chỉnh lại theo định dạng "mm/dd/yyyy" để lọc dữ liệu cho đúng. 
    - Tạo 1 cái Sub tạm đặt tên là CopyDLData2 (). Code như bên dưới. Tạo nút lệnh thực thi tương tự của bác Xuân Thanh.

    Mã PHP:
    Option Compare Database
    Option Explicit

    Public Const conJetDate "\#mm\/dd\/yyyy\#" 

    Mã PHP:
    Sub CopyDLDaTa2(ThangNam)

       Dim sPath As String
       Dim sSQL 
    As String
       Dim StartDate 
    As DateStopDate As Date
        
       sPath 
    "\\Mac\Home\Downloads\DeMo Khoa So\LuuDuLieu.accdb"    'Chinh lai duong dan'
       StartDate DateSerial(NamThang1)
       StopDate DateSerial(NamThang 11) - 1
       
       strSQL 
    "INSERT INTO tblLuuDuLieu IN '" sPath "' SELECT * FROM tblNhatKy WHERE Ngay>=" Format(StartDate,conJetDate) & " AND Ngay <=" Format(StopDateconJetDate)
       Debug.Print strSQL
       CurrentDb
    .Execute strSQLdbFailOnError

    End Sub 

    [Hình: 43041230725_2e80ba6644_o.png]
  • RE: DBEngine và Workspace trong việc truyền tải dữ liệu giữa hai Database

    NguyenDungAnh > 11-08-18, 11:41 AM

    Bác nói rõ hơn cách insert query vào table được không em chưa hiểu rõ cái này
  • RE: DBEngine và Workspace trong việc truyền tải dữ liệu giữa hai Database

    Xuân Thanh > 11-08-18, 11:49 AM

    (09-08-18, 09:25 PM)ongke0711 Đã viết: Trước đây cũng có một bài của maidinhdan cũng gần với cái cách dùng DBEngine về việc Append dữ liệu giữa 2 file Access khác nhau. Các bạn cũng có thể tham khảo thêm.
    Link: http://thuthuataccess.com/forum/thread-9572.html

    Tôi cũng đóng góp 1 cách khác:
    - Không cần khai báo Database cần backup cho DBEngine Collection.
    - Dùng câu lệnh SQL để Insert dữ liệu lên table đích. Vì câu lệnh SQL luôn xử lý nhanh hơn DAO Recordset. "Insert Into..." nó cập nhật một lúc nguyên một bó records (batch update), còn đối với DAO Recordset thì nó Loop qua từng record để update.
      
        INSERT INTO tên Table IN 'đường dẫn file database đích để lưu' SELECT * FROM Table WHERE ...

    - Vì dùng câu lệnh SQL nên biến ngày/tháng phải chỉnh lại theo định dạng "mm/dd/yyyy" để lọc dữ liệu cho đúng. 
    - Tạo 1 cái Sub tạm đặt tên là CopyDLData2 (). Code như bên dưới. Tạo nút lệnh thực thi tương tự của bác Xuân Thanh.

    Mã PHP:
    Option Compare Database
    Option Explicit

    Public Const conJetDate "\#mm\/dd\/yyyy\#" 

    Mã PHP:
    Sub CopyDLDaTa2(ThangNam)

       Dim sPath As String
       Dim sSQL 
    As String
       Dim StartDate 
    As DateStopDate As Date
        
       sPath 
    "\\Mac\Home\Downloads\DeMo Khoa So\LuuDuLieu.accdb"    'Chinh lai duong dan'
       StartDate DateSerial(NamThang1)
       StopDate DateSerial(NamThang 11) - 1
       
       strSQL 
    "INSERT INTO tblLuuDuLieu IN '" sPath "' SELECT * FROM tblNhatKy WHERE Ngay>=" Format(StartDate,conJetDate) & " AND Ngay <=" Format(StopDateconJetDate)
       Debug.Print strSQL
       CurrentDb
    .Execute strSQLdbFailOnError

    End Sub 

    [Hình: 43041230725_2e80ba6644_o.png]

    1/ Chính xác là như vậy. Nếu dùng lệnh Insert thì đâu cần dùng tới DAo DBEngine. Úp thẳng nó vô Table là OK
    Nhưng đây đang nói về DAO nên phải dùng Do...Loop. Nếu không dùng DAO thì dùng SQL để úp thẳng vô

    2/ Sory maidinhdan vì chưa đọc bài của bạn nên post bài này
  • RE: DBEngine và Workspace trong việc truyền tải dữ liệu giữa hai Database

    ongke0711 > 12-08-18, 06:51 PM

    Anh Xuân Thanh đã nói về cái DBEngine nên sẳn đây tôi cũng nói thêm chút về:
            
             DBEngine(0)(0) và CurrentDb

    [Hình: 43980246221_74f4419923_z.jpg]

    Như ví dụ của anh Xuân Thanh ở trên dùng: DBEngine.Workspaces (0).OpenDatabase (sPath) để mở một file .mdb khác với file .mdb mình đang thao tác. Từ đó bạn thấy được rằng DBEngine.Workspaces( ) là một Database Collections, bạn có thể nạp các database (.mdb, .accdb) vô cái Workspaces() rồi gọi nó theo số thứ tự. 
    VD: DBEngine.Workspaces (0).Database(1) là cái database bên ngoài (thứ 2) cần mở để lưu dữ liệu (là file luudulieu.accdb trong ví dụ trên)

    Còn CurrentDb() mà chúng ta cũng hay dùng khi khai báo: Set db = CurrentDb chính là cái ứng dụng Access mà ta đang xử lý trên cái giao diện (UI) của nó (có các ngăn Table, Query, Form, Report, Module).

    Vậy cái DBEngine.Workspaces(0).Database(0) sẽ trả về chính là database mà ta đang thao tác và cũng là database trả về của hàm CurrentDb().
    DBEngine.Workspaces (0).Database(0) còn có khác viết khác cho gọn là: DBEngine (0)(0)
    (cái ký hiệu mảng (0)(0) này mà dùng font chữ Georgia nhìn cũng hay hay... 014 )

    Bài viết dưới đây chỉ nói về tham chiếu đến cái Database hiện tại (đang thao tác) tức là về DBEngine(0)(0) và CurrentDB().
    Việc tham chiếu đến Database bên ngoài thì dùng DBEngine(0).OpenDatabase (pathDB) là cách không phải bàn cãi.

    DBEngine(0)(0) và CurrentDb() cũng có điểm khác nhau.

    * Database trả về có được cập nhật hay không?

    CurrentDb() là một hàm Access. Nó được Access (trên giao diện ứng dụng) xử lý để tham chiếu tới cái database hiện thời và tạo một bản sao database của nó. Mỗi khi được gọi, CurrentDb() sẽ luôn tự động Refresh các bộ (collection) Form, Query nên luôn trả về 1 bản sao database mới và đã được cập nhật mới nhất (các form, query... mới thêm vào).

    Trong khi đó DBEngine(0)(0) tham chiếu thẳng đến cái DAO (Data Access Object) của bộ máy JET nên cái database được tham chiếu này chưa hẳn đã được cập nhật các đối tượng như form, query. Nói dễ hiểu là nó sẽ trả về cái Database với các bộ Table, Form, Query...ngay khi Ms Access mở file .mdb. Sau đó nếu bạn có thiết kế thêm Table, Query thì cái database của DBEngine(0)(0) này sẽ chưa có chúng trong đó.  Muốn cái DBEngine(0)(0) này được cập nhật mới nhất thì mỗi khi gọi phải Refresh các bộ (Collections) table, query...mà việc Refresh này cũng ảnh hưởng đến tốc độ thực thi không ít.
              DBEngine(0)(0).QueryDefs.Refresh

    * Tốc độ thực thi:

    Nếu không cần sử dụng cái database được cập nhật mới nhất (không cần Refresh) thì DBEngine(0)(0) sẽ cho tốc độ xử lý nhanh hơn gấp nhiều lần so với dùng CurrentDb(). 
    (có những trường hợp test nhanh hơn 17 - 60 lần)

    Như đã nó ở trên, khi gọi CurrentDb() thì hàm này sẽ tự động refresh các forms, query collection và trả về là 1 bản sao của cái database mới vừa được Refresh -> do đó nó sẽ phải dùng thêm bộ nhớ (memory) cho tác vụ này. Ngược lại thì DBEngine(0)(0) tham chiếu thẳng tới cái database đang có sẳn trong bộ nhớ khi Access mở file, không dùng thêm bộ nhớ cho bản copy database. 

    Vậy nếu đọc những đều trên thì chúng ta sẽ suy nghĩ là sau này nên dùng DBEngine(0)(0) thay cho CurrentDb() trong những tác vụ mà không cần cái database cập nhật (chẳng hạn như: Insert thêm dòng vào table...). Nhưng DBEngine(0)(0) có một vấn đề là: trong vài trường hợp nó chưa chắc chỉ đến cái database hiện thời mà bạn đang thao tác, viết code. Một cái là database mà Ms Access đang xử lý trên giao diện ứng dụng của nó, một cái là database mà bộ máy JET của Access đang xử lý. (JET gọi nôm na là bộ não của Ms Access). Lỗi này xảy ra khi Access chạy các Form, report... wizards, từ đó có thể gây ra cái database hiện thời không nằm trong vị trí đầu tiên (0) của Workspace nữa. Muốn gán database hiện thời vào vị trí đầu tiên của Workspaces thì phải chạy lệnh Refresh.
             DBEngine(0).Databases.Refresh

    Do đó cách an toàn nhất vẫn là dùng CurrentDb() để tránh tham chiếu sai database cũng như database không được cập nhật các table, query mới thêm vào. Bạn có thể mở bao nhiêu cái database tuỳ ý và đóng chúng lại khi không cần dùng nữa.


    Một số lưu ý khi dùng CurrentDb và DBEngine(0)(0):

    - Đóng database sau khi xử lý.

    Mã PHP:
    Dim db As DAO.Database

    Set db
    =CurrentDB
    ...

    'db.Close  <=Đóng hoặc không đóng cái "db" này cũng không sao'
    Set db=Nothing 

    Mã PHP:
    Dim db As DAO.Database

    Set db
    =DBEngine(0)(0)
    ...

    'db.Close  <=Không nên đóng cái "db" này tránh Access có thể bị crash'
    Set db=Nothing 

    Mã PHP:
    Dim db As DAO.Database

    Set db
    =DBEngine(0).OpenDatabase (sPath)
    ...

    db.Close  '<=Nên đóng cái "db" này vì nó là database bên ngoài'
    Set db=Nothing 


    - Ngoài ra khi dùng CurrentDb(), bạn nên Set một biến đối tượng cho nó, không nên dùng trực tiếp vì sẽ có trường hợp báo lỗi.
    Ví dụ:
    'Code lỗi:
        Dim tbl As DAO.TableDef
        Set tbl = CurrentDb.TableDefs(0) ==> đối tượng database sẽ mất sau dòng này.
        Debug.Print tbl.Name

    --> Code trên sẽ báo lỗi vì cái đối tượng database hiện tại sẽ mất đi ngay khi cái hàm CurrentDb được thực thi xong-> dòng lệnh kế tiếp "Debug.Print tbl.Name" sẽ không chạy được.

    'Code không bị lỗi:
        Dim db AS DAO.Database
        Dim tbl As DAO.TableDef
        Set db = CurrentDb
        Set tbl = db.TableDefs(0)
        Debug.Print tbl.Name


    --> biến đối tượng database "db" không mất đi cho đến khi bạn đóng nó: db.Close

    ---------------------------------------------------------------

    Từ những điểm mạnh yếu của DBEngine (0)(0) và CurrentDb(), người ta đã viết ra cái hàm khác thay thế để tận dụng tốc độ của DBEngine (không cần Refresh các table, query, form... collections)  và việc trả về đúng cái database hiện thời của hàm CurrentDb.

    - Hàm bên dưới do David-W-Fenton viết. Bạn copy nó vào Module đặt tên tuỳ ý.

    Mã PHP:
    Option Compare Database
    Option Explicit

    Public Function dbLocal(Optional blnCleanup As Boolean False) As DAO.Database

       On Error 
    GoTo errHandler
       Static dbCurrent 
    As DAO.Database

       If blnCleanup Then 
    GoTo closeDB

    retryDB
    :
       If dbCurrent Is Nothing Then
           Set dbCurrent 
    CurrentDb()
       End If

    exitRoutine:
       Set dbLocal dbCurrent
       Exit 
    Function

    closeDB:
       If Not (dbCurrent Is NothingThen
           
    'dbCurrent.close ' this never has any effect
           Set dbCurrent 
    Nothing
       End 
    If
       GoTo exitRoutine

    errHandler
    :
       Select Case Err.Number
       Case 3420    
    ' Object invalid or no longer set.'
           Set dbCurrent Nothing
           If Not blnCleanup Then
               Resume retryDB
           Else
               Resume closeDB
           End 
    If
       Case Else
           MsgBox Err.Number ": " Err.DescriptionvbExclamation"Error in dbLocal()"
           Resume exitRoutine
       End Select
       
    End 
    Function 


    - Sử dụng trong ứng dụng:
      + Không cần khai báo: Dim db DAO.Database, Set db =...
      + Gọi trực tiếp:
                 Set rs = dbLocal.OpenRecordSet ("strSQL...")
         hoặc
                 dbLocal.Execute (strSQL)
      + Chú ý khi đóng ứng dụng thì nên gọi:
                 Call dbLocal (False)
      + Khi có thêm các query, table vào ứng dụng, cái biến tĩnh database của bạn đã lưu trên cache không được cập nhật nên bạn phải refresh đối tượng database.
                 dbLocal.QueryDefs.Refresh
            
    -----------------------------------------------------------

    Tóm lại:

    - Dùng DBEngine.Workspaces.OpenDatabase ("đường dẫn tới file mdb") để tham chiếu tới database bên ngoài cái giao diện database đang thao tác.

    - Dùng DBEgine(0)(0) hoặc CurrentDb() đều tham chiếu tới database hiện tại nhưng DBEngine sẽ xử lý nhanh hơn so với CurrentDb nhưng database sẽ không được cập nhật và có thể trong vài trường hợp không tham chiếu đến database hiện tại.

    - Giải pháp hàm dbLocal() của David-W-Fenton tích hợp ưu điểm tốc độ của DBEngine và khắc phục việc tham chiếu sai tới database hiện thời.




    (Nguồn st stackoverflow.com)
  • RE: DBEngine và Workspace trong việc truyền tải dữ liệu giữa hai Database

    Xuân Thanh > 12-08-18, 08:29 PM

    Tốc độ xử lý sẽ rất nhanh khi dùng hàm dbLocalI() thay cho CurrentDb(). Ngày xưa tôi hay dùng cái này nhưng viết ngắn hơn nên phải dùng thêm cái lệnh close nó
    Thank you