Bộ sưu tập Cloud Firestore


Câu trả lời:


188

Cập nhật (tháng 4 năm 2019) - FieldValue.increment (xem giải pháp bộ sưu tập lớn)


Như với nhiều câu hỏi, câu trả lời là - Nó phụ thuộc .

Bạn nên rất cẩn thận khi xử lý một lượng lớn dữ liệu ở mặt trước. Ngoài việc làm cho mặt trước của bạn cảm thấy chậm chạp, Firestore còn tính phí cho bạn $ 0,60 trên mỗi triệu lượt đọc bạn thực hiện.


Bộ sưu tập nhỏ (dưới 100 tài liệu)

Sử dụng cẩn thận - Trải nghiệm người dùng Frontend có thể bị ảnh hưởng

Xử lý điều này ở mặt trước sẽ tốt miễn là bạn không làm quá nhiều logic với mảng trả về này.

db.collection('...').get().then(snap => {
   size = snap.size // will return the collection size
});

Bộ sưu tập trung bình (100 đến 1000 tài liệu)

Sử dụng cẩn thận - Yêu cầu đọc Firestore có thể tốn rất nhiều

Xử lý việc này ở mặt trước là không khả thi vì nó có quá nhiều tiềm năng để làm chậm hệ thống người dùng. Chúng ta nên xử lý phía máy chủ logic này và chỉ trả lại kích thước.

Hạn chế của phương pháp này là bạn vẫn đang thực hiện các lần đọc Firestore (bằng với kích thước của bộ sưu tập của bạn), về lâu dài có thể khiến bạn tốn nhiều tiền hơn dự kiến.

Chức năng đám mây:

...
db.collection('...').get().then(snap => {
    res.status(200).send({length: snap.size});
});

Mặt trước:

yourHttpClient.post(yourCloudFunctionUrl).toPromise().then(snap => {
     size = snap.length // will return the collection size
})

Bộ sưu tập lớn (hơn 1000 tài liệu)

Giải pháp mở rộng nhất


FieldValue.increment ()

Kể từ tháng 4 năm 2019 Firestore hiện cho phép tăng các bộ đếm, hoàn toàn nguyên tử và không cần đọc dữ liệu trước đó . Điều này đảm bảo chúng tôi có các giá trị truy cập chính xác ngay cả khi cập nhật từ nhiều nguồn đồng thời (đã giải quyết trước đó bằng các giao dịch), đồng thời giảm số lần đọc cơ sở dữ liệu mà chúng tôi thực hiện.


Bằng cách lắng nghe bất kỳ tài liệu nào xóa hoặc tạo, chúng ta có thể thêm hoặc xóa khỏi trường đếm đang ngồi trong cơ sở dữ liệu.

Xem các tài liệu về Firestore - Bộ đếm phân tán Hoặc có cái nhìn về Tập hợp dữ liệu của Jeff Delaney. Hướng dẫn của anh ấy thực sự tuyệt vời cho bất cứ ai sử dụng AngularFire nhưng bài học của anh ấy cũng nên chuyển sang các khuôn khổ khác.

Chức năng đám mây:

export const documentWriteListener = 
    functions.firestore.document('collection/{documentUid}')
    .onWrite((change, context) => {

    if (!change.before.exists) {
        // New document Created : add one to count

        db.doc(docRef).update({numberOfDocs: FieldValue.increment(1)});

    } else if (change.before.exists && change.after.exists) {
        // Updating existing document : Do nothing

    } else if (!change.after.exists) {
        // Deleting document : subtract one from count

        db.doc(docRef).update({numberOfDocs: FieldValue.increment(-1)});

    }

return;
});

Bây giờ trên frontend, bạn chỉ có thể truy vấn trường numberOfDocs này để lấy kích thước của bộ sưu tập.


17
Giải pháp tuyệt vời cho các bộ sưu tập lớn! Tôi chỉ muốn thêm rằng những người triển khai nên gói và đọc thành một firestore.runTransaction { ... }khối. Điều này khắc phục các vấn đề tương tranh với việc truy cập numberOfDocs.
efemoney

2
Các phương pháp này đang sử dụng một bản tường thuật về số lượng hồ sơ. Nếu bạn sử dụng bộ đếm và tăng bộ đếm bằng giao dịch, điều đó sẽ không đạt được kết quả tương tự nếu không có chi phí và nhu cầu bổ sung của chức năng đám mây?
dùng3836415

10
Giải pháp cho các bộ sưu tập lớn không phải là idempotent và không hoạt động ở bất kỳ quy mô nào. Trình kích hoạt tài liệu Firestore được đảm bảo chạy ít nhất một lần, nhưng có thể chạy nhiều lần. Khi điều này xảy ra, thậm chí duy trì cập nhật bên trong một giao dịch có thể chạy nhiều lần, điều này sẽ cung cấp cho bạn một số sai. Khi tôi thử điều này, tôi gặp vấn đề với ít hơn một chục sáng tạo tài liệu cùng một lúc.
Tym Pollack

2
Xin chào @TymPollack. Tôi đã nhận thấy một số hành vi không nhất quán sử dụng kích hoạt đám mây. Bất kỳ cơ hội nào bạn có thể liên kết với tôi và bài viết hoặc diễn đàn để giải thích hành vi bạn đã trải qua?
Matthew Mullin

2
@cmprogram bạn đang đọc toàn bộ bộ sưu tập và dữ liệu khi sử dụng db.collection ('...') ... vì vậy khi bạn không cần dữ liệu thì bạn đã đúng - bạn có thể dễ dàng yêu cầu danh sách ID bộ sưu tập (không phải dữ liệu tài liệu thu thập) và nó được tính là một lần đọc.
atereshkov

24

Cách đơn giản nhất để làm như vậy là đọc kích thước của "querySnapshot".

db.collection("cities").get().then(function(querySnapshot) {      
    console.log(querySnapshot.size); 
});

Bạn cũng có thể đọc độ dài của mảng tài liệu bên trong "querySnapshot".

querySnapshot.docs.length;

Hoặc nếu một "querySnapshot" trống bằng cách đọc giá trị trống, nó sẽ trả về giá trị boolean.

querySnapshot.empty;

73
Hãy lưu ý rằng mỗi tài liệu "chi phí" một lần đọc. Vì vậy, nếu bạn đếm 100 mục trong một bộ sưu tập theo cách này, bạn sẽ bị tính phí cho 100 lần đọc!
Georg

Đúng, nhưng không có cách nào khác để tổng hợp số lượng tài liệu trong một bộ sưu tập. Và nếu bạn đã tìm nạp bộ sưu tập, việc đọc mảng "tài liệu" sẽ không yêu cầu tìm nạp thêm, do đó sẽ không "tốn" thêm số lần đọc.
Ompel

5
Điều này đọc tất cả các tài liệu trong bộ nhớ! Chúc may mắn với điều đó cho các bộ dữ liệu lớn ...
Dan Dascalescu

84
Điều này thực sự không thể tin được rằng Firebase Firestore không có loại db.collection.count(). Suy nghĩ chỉ thả chúng cho việc này
Blue Bot

8
Đặc biệt đối với các bộ sưu tập lớn, thật không công bằng khi tính phí chúng tôi như thể chúng tôi thực sự tải xuống và sử dụng tất cả các tài liệu. Đếm cho một bảng (bộ sưu tập) là một tính năng cơ bản như vậy. Xem xét mô hình định giá của họ và Firestore đã được ra mắt vào năm 2017, thật không thể tin được rằng Google không cung cấp một cách khác để có được kích thước của một bộ sưu tập. Cho đến khi họ không thực hiện nó, ít nhất họ nên tránh tính phí cho nó.
niết bàn

23

Theo như tôi biết thì không có giải pháp tích hợp nào cho việc này và nó chỉ có thể có trong nút sdk ngay bây giờ. Nếu bạn có một

db.collection('someCollection')

bạn có thể dùng

.select([fields])

để xác định trường bạn muốn chọn. Nếu bạn chọn select () trống, bạn sẽ chỉ nhận được một mảng các tài liệu tham khảo.

thí dụ:

db.collection('someCollection').select().get().then( (snapshot) => console.log(snapshot.docs.length) );

Giải pháp này chỉ là tối ưu hóa cho trường hợp xấu nhất là tải xuống tất cả tài liệu và không mở rộng trên các bộ sưu tập lớn!

Ngoài ra, hãy xem điều này:
Cách lấy số lượng tài liệu trong bộ sưu tập với Cloud Firestore


Theo kinh nghiệm của tôi, select(['_id'])nhanh hơnselect()
JAnton

13

Hãy cẩn thận đếm số lượng tài liệu cho các bộ sưu tập lớn . Đó là một chút phức tạp với cơ sở dữ liệu Firestore nếu bạn muốn có một bộ đếm tiền định sẵn cho mỗi bộ sưu tập.

Mã như thế này không hoạt động trong trường hợp này:

export const customerCounterListener = 
    functions.firestore.document('customers/{customerId}')
    .onWrite((change, context) => {

    // on create
    if (!change.before.exists && change.after.exists) {
        return firestore
                 .collection('metadatas')
                 .doc('customers')
                 .get()
                 .then(docSnap =>
                     docSnap.ref.set({
                         count: docSnap.data().count + 1
                     }))
    // on delete
    } else if (change.before.exists && !change.after.exists) {
        return firestore
                 .collection('metadatas')
                 .doc('customers')
                 .get()
                 .then(docSnap =>
                     docSnap.ref.set({
                         count: docSnap.data().count - 1
                     }))
    }

    return null;
});

Lý do là bởi vì mọi kích hoạt Firestore trên đám mây phải là idempotent, như tài liệu về Firestore nói: https://firebase.google.com/docs/fifts/firestore-events#limemony_and_guarantees

Giải pháp

Vì vậy, để ngăn chặn nhiều lần thực thi mã của bạn, bạn cần quản lý bằng các sự kiện và giao dịch. Đây là cách đặc biệt của tôi để xử lý các bộ sưu tập lớn:

const executeOnce = (change, context, task) => {
    const eventRef = firestore.collection('events').doc(context.eventId);

    return firestore.runTransaction(t =>
        t
         .get(eventRef)
         .then(docSnap => (docSnap.exists ? null : task(t)))
         .then(() => t.set(eventRef, { processed: true }))
    );
};

const documentCounter = collectionName => (change, context) =>
    executeOnce(change, context, t => {
        // on create
        if (!change.before.exists && change.after.exists) {
            return t
                    .get(firestore.collection('metadatas')
                    .doc(collectionName))
                    .then(docSnap =>
                        t.set(docSnap.ref, {
                            count: ((docSnap.data() && docSnap.data().count) || 0) + 1
                        }));
        // on delete
        } else if (change.before.exists && !change.after.exists) {
            return t
                     .get(firestore.collection('metadatas')
                     .doc(collectionName))
                     .then(docSnap =>
                        t.set(docSnap.ref, {
                            count: docSnap.data().count - 1
                        }));
        }

        return null;
    });

Các trường hợp sử dụng ở đây:

/**
 * Count documents in articles collection.
 */
exports.articlesCounter = functions.firestore
    .document('articles/{id}')
    .onWrite(documentCounter('articles'));

/**
 * Count documents in customers collection.
 */
exports.customersCounter = functions.firestore
    .document('customers/{id}')
    .onWrite(documentCounter('customers'));

Như bạn có thể thấy, chìa khóa để ngăn chặn nhiều thực thi là thuộc tính được gọi là eventId trong đối tượng bối cảnh. Nếu chức năng đã được xử lý nhiều lần cho cùng một sự kiện, id sự kiện sẽ giống nhau trong mọi trường hợp. Thật không may, bạn phải có bộ sưu tập "sự kiện" trong cơ sở dữ liệu của mình.


2
Họ đang diễn đạt điều này như thể hành vi này sẽ được sửa trong bản phát hành 1.0. Các chức năng AWS của Amazon bị vấn đề tương tự. Một cái gì đó đơn giản như đếm các lĩnh vực trở nên phức tạp và đắt tiền.
MarcG

Đi để thử điều này ngay bây giờ vì nó có vẻ là một giải pháp tốt hơn. Bạn có quay trở lại và thanh lọc bộ sưu tập sự kiện của bạn bao giờ? Tôi đã nghĩ đến việc chỉ cần thêm một trường ngày và thanh lọc cũ hơn một ngày hoặc một cái gì đó chỉ để giữ cho dữ liệu được đặt nhỏ (có thể là 1mil + sự kiện / ngày). Trừ khi có một cách dễ dàng trong FS để làm điều đó ... chỉ sử dụng FS một vài tháng.
Tym Pollack

1
Chúng ta có thể xác minh rằng context.eventIdsẽ luôn giống nhau trên nhiều lệnh của cùng một trình kích hoạt không? Trong thử nghiệm của tôi, nó có vẻ phù hợp, nhưng tôi không thể tìm thấy bất kỳ tài liệu "chính thức" nào nêu rõ điều này.
Mike McLin

2
Vì vậy, sau khi sử dụng một thời gian, tôi đã thấy rằng, trong khi giải pháp này hoạt động với chính xác một lần viết, thật tuyệt, nếu có quá nhiều trình kích hoạt từ nhiều tài liệu được viết cùng một lúc và cố gắng cập nhật cùng một tài liệu đếm, bạn có thể nhận lỗi tranh chấp từ Firestore. Bạn đã gặp phải những điều đó, và làm thế nào bạn có được xung quanh nó? (Lỗi: 10 CÓ THỂ: Quá nhiều tranh cãi về các tài liệu này. Vui lòng thử lại.)
Tym Pollack

1
@TymPollack nhìn vào các tài liệu của bộ đếm phân tán ghi được giới hạn ở khoảng một bản cập nhật mỗi giây
Jamie

8

Vào năm 2020, điều này vẫn chưa có trong SDK Firebase tuy nhiên nó có sẵn trong Firebase Extension (Beta) tuy nhiên việc cài đặt và sử dụng ... khá phức tạp.

Một cách tiếp cận hợp lý

Người trợ giúp ... (tạo / xóa có vẻ dư thừa nhưng rẻ hơn so với onUpdate)

export const onCreateCounter = () => async (
  change,
  context
) => {
  const collectionPath = change.ref.parent.path;
  const statsDoc = db.doc("counters/" + collectionPath);
  const countDoc = {};
  countDoc["count"] = admin.firestore.FieldValue.increment(1);
  await statsDoc.set(countDoc, { merge: true });
};

export const onDeleteCounter = () => async (
  change,
  context
) => {
  const collectionPath = change.ref.parent.path;
  const statsDoc = db.doc("counters/" + collectionPath);
  const countDoc = {};
  countDoc["count"] = admin.firestore.FieldValue.increment(-1);
  await statsDoc.set(countDoc, { merge: true });
};

export interface CounterPath {
  watch: string;
  name: string;
}

Móc Firestore xuất khẩu


export const Counters: CounterPath[] = [
  {
    name: "count_buildings",
    watch: "buildings/{id2}"
  },
  {
    name: "count_buildings_subcollections",
    watch: "buildings/{id2}/{id3}/{id4}"
  }
];


Counters.forEach(item => {
  exports[item.name + '_create'] = functions.firestore
    .document(item.watch)
    .onCreate(onCreateCounter());

  exports[item.name + '_delete'] = functions.firestore
    .document(item.watch)
    .onDelete(onDeleteCounter());
});

Trong hành động

Bộ sưu tập gốc tòa nhà và tất cả các bộ sưu tập phụ sẽ được theo dõi.

nhập mô tả hình ảnh ở đây

Ở đây dưới /counters/đường dẫn gốc

nhập mô tả hình ảnh ở đây

Bây giờ số lượng bộ sưu tập sẽ cập nhật tự động và cuối cùng! Nếu bạn cần một số đếm, chỉ cần sử dụng đường dẫn bộ sưu tập và tiền tố với nó counters.

const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts';
const collectionCount = await db
  .doc('counters/' + collectionPath)
  .get()
  .then(snap => snap.get('count'));

Đây có phải chịu cùng giới hạn "1 cập nhật tài liệu mỗi giây" không?
Ayyappa

Có, nhưng cuối cùng là phù hợp, có nghĩa là số lượng bộ sưu tập cuối cùng sẽ phù hợp với số lượng bộ sưu tập thực tế, đó là giải pháp dễ thực hiện nhất và trong nhiều trường hợp, độ trễ trong số đếm là chấp nhận được.
Ben Winding

7

Tôi đồng ý với @Matthew, sẽ tốn rất nhiều chi phí nếu bạn thực hiện truy vấn đó.

[LỜI KHUYÊN CHO CÁC NHÀ PHÁT TRIỂN TRƯỚC KHI BẮT ĐẦU CÁC DỰ ÁN]

Vì chúng tôi đã thấy trước tình huống này ngay từ đầu, nên chúng tôi thực sự có thể tạo ra một bộ sưu tập cụ thể là các bộ đếm với một tài liệu để lưu trữ tất cả các bộ đếm trong một trường có loại number.

Ví dụ:

Đối với mỗi thao tác CRUD trên bộ sưu tập, hãy cập nhật tài liệu truy cập:

  1. Khi bạn tạo một bộ sưu tập / bộ sưu tập mới: (+1 trong bộ đếm) [1 thao tác ghi]
  2. Khi bạn xóa bộ sưu tập / bộ sưu tập con: (-1 trong bộ đếm) [thao tác ghi 1]
  3. Khi bạn cập nhật bộ sưu tập / bộ sưu tập hiện có, không làm gì trên tài liệu truy cập: (0)
  4. Khi bạn đọc một bộ sưu tập / bộ sưu tập hiện có, không làm gì trên tài liệu truy cập: (0)

Lần tới, khi bạn muốn lấy số lượng bộ sưu tập, bạn chỉ cần truy vấn / trỏ đến trường tài liệu. [1 thao tác đọc]

Ngoài ra, bạn có thể lưu trữ tên bộ sưu tập trong một mảng, nhưng điều này sẽ rất khó khăn, điều kiện của mảng trong firebase được hiển thị như dưới đây:

// we send this
['a', 'b', 'c', 'd', 'e']
// Firebase stores this
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e'}

// since the keys are numeric and sequential,
// if we query the data, we get this
['a', 'b', 'c', 'd', 'e']

// however, if we then delete a, b, and d,
// they are no longer mostly sequential, so
// we do not get back an array
{2: 'c', 4: 'e'}

Vì vậy, nếu bạn không xóa bộ sưu tập, bạn thực sự có thể sử dụng mảng để lưu danh sách tên bộ sưu tập thay vì truy vấn tất cả bộ sưu tập mỗi lần.

Hy vọng nó giúp!


Đối với một bộ sưu tập nhỏ, có thể. Nhưng hãy nhớ rằng giới hạn kích thước tài liệu của Firestore là ~ 1 MB, nếu ID tài liệu trong bộ sưu tập được tạo tự động (20 byte), thì bạn sẽ chỉ có thể lưu trữ ~ 52.425 trong số đó trước khi tài liệu giữ mảng to quá. Tôi đoán như một cách giải quyết rằng bạn có thể tạo một tài liệu mới sau mỗi 50.000 phần tử, nhưng sau đó việc duy trì các mảng đó sẽ hoàn toàn không thể quản lý được. Hơn nữa, khi kích thước tài liệu tăng lên, sẽ mất nhiều thời gian hơn để đọc và cập nhật, điều này cuối cùng sẽ khiến bất kỳ hoạt động nào khác trên đó hết thời gian tranh chấp.
Tym Pollack

5

Không, không có hỗ trợ tích hợp cho các truy vấn tổng hợp ngay bây giờ. Tuy nhiên, có một vài điều bạn có thể làm.

Đầu tiên là tài liệu ở đây . Bạn có thể sử dụng các giao dịch hoặc chức năng đám mây để duy trì thông tin tổng hợp:

Ví dụ này cho thấy cách sử dụng một hàm để theo dõi số lượng xếp hạng trong một bộ sưu tập con, cũng như xếp hạng trung bình.

exports.aggregateRatings = firestore
  .document('restaurants/{restId}/ratings/{ratingId}')
  .onWrite(event => {
    // Get value of the newly added rating
    var ratingVal = event.data.get('rating');

    // Get a reference to the restaurant
    var restRef = db.collection('restaurants').document(event.params.restId);

    // Update aggregations in a transaction
    return db.transaction(transaction => {
      return transaction.get(restRef).then(restDoc => {
        // Compute new number of ratings
        var newNumRatings = restDoc.data('numRatings') + 1;

        // Compute new average rating
        var oldRatingTotal = restDoc.data('avgRating') * restDoc.data('numRatings');
        var newAvgRating = (oldRatingTotal + ratingVal) / newNumRatings;

        // Update restaurant info
        return transaction.update(restRef, {
          avgRating: newAvgRating,
          numRatings: newNumRatings
        });
      });
    });
});

Giải pháp mà jbb đề cập cũng hữu ích nếu bạn chỉ muốn đếm tài liệu không thường xuyên. Đảm bảo sử dụng select()câu lệnh để tránh tải xuống tất cả từng tài liệu (đó là rất nhiều băng thông khi bạn chỉ cần đếm). select()hiện chỉ có sẵn trong SDK máy chủ để giải pháp không hoạt động trong ứng dụng di động.


1
Giải pháp này không phải là bình thường, do đó, bất kỳ trình kích hoạt nào kích hoạt nhiều lần sẽ làm mất số lượng xếp hạng và trung bình của bạn.
Tym Pollack

4

Không có tùy chọn trực tiếp có sẵn. Bạn không thể làm được db.collection("CollectionName").count(). Dưới đây là hai cách mà bạn có thể tìm thấy số lượng tài liệu trong một bộ sưu tập.

1: - Nhận tất cả các tài liệu trong bộ sưu tập và sau đó lấy kích thước của nó (Không phải là Giải pháp tốt nhất)

db.collection("CollectionName").get().subscribe(doc=>{
console.log(doc.size)
})

Bằng cách sử dụng mã ở trên, tài liệu của bạn đọc sẽ bằng với kích thước của tài liệu trong bộ sưu tập và đó là lý do tại sao người ta phải tránh sử dụng giải pháp trên.

2: - Tạo một tài liệu riêng trong bộ sưu tập của bạn, nơi sẽ lưu trữ số lượng tài liệu trong bộ sưu tập. (Giải pháp tốt nhất)

db.collection("CollectionName").doc("counts")get().subscribe(doc=>{
console.log(doc.count)
})

Ở trên, chúng tôi đã tạo một tài liệu với số lượng tên để lưu trữ tất cả thông tin đếm. Bạn có thể cập nhật tài liệu đếm theo cách sau: -

  • Tạo một kích hoạt Firestore trên số lượng tài liệu
  • Tăng thuộc tính đếm của tài liệu đếm khi tài liệu mới được tạo.
  • Giảm thuộc tính đếm của tài liệu đếm khi tài liệu bị xóa.

giá wrt (Tài liệu đã đọc = 1) và truy xuất dữ liệu nhanh, giải pháp trên là tốt.


3

Tăng một bộ đếm bằng admin.firestore.FieldValue.increment :

exports.onInstanceCreate = functions.firestore.document('projects/{projectId}/instances/{instanceId}')
  .onCreate((snap, context) =>
    db.collection('projects').doc(context.params.projectId).update({
      instanceCount: admin.firestore.FieldValue.increment(1),
    })
  );

exports.onInstanceDelete = functions.firestore.document('projects/{projectId}/instances/{instanceId}')
  .onDelete((snap, context) =>
    db.collection('projects').doc(context.params.projectId).update({
      instanceCount: admin.firestore.FieldValue.increment(-1),
    })
  );

Trong ví dụ này, chúng tôi tăng một instanceCounttrường trong dự án mỗi khi một tài liệu được thêm vào instancesbộ sưu tập phụ. Nếu trường không tồn tại, nó sẽ được tạo và tăng lên 1.

Gia số là giao dịch nội bộ nhưng bạn nên sử dụng bộ đếm phân tán nếu bạn cần tăng thường xuyên hơn mỗi 1 giây.

Bạn thường nên thực hiện onCreateonDeletethay onWritevì bạn sẽ gọi onWritecho các bản cập nhật, điều đó có nghĩa là bạn đang chi nhiều tiền hơn cho các yêu cầu chức năng không cần thiết (nếu bạn cập nhật các tài liệu trong bộ sưu tập của mình).


2

Một cách giải quyết là:

viết một bộ đếm trong tài liệu firebase, mà bạn tăng trong một giao dịch mỗi khi bạn tạo một mục mới

Bạn lưu trữ số đếm trong một trường của mục nhập mới của bạn (ví dụ: vị trí: 4).

Sau đó, bạn tạo một chỉ mục trên trường đó (vị trí DESC).

Bạn có thể thực hiện bỏ qua + giới hạn với một truy vấn. Trường hợp ("vị trí", "<" x) .OrderBy ("vị trí", DESC)

Hi vọng điêu nay co ich!


1

Tôi đã tạo ra một hàm phổ quát sử dụng tất cả các ý tưởng này để xử lý tất cả các tình huống truy cập (ngoại trừ truy vấn).

Ngoại lệ duy nhất là khi nhiều người viết một giây, nó làm bạn chậm lại. Một ví dụ sẽ là lượt thích trên một bài viết theo xu hướng. Nó là quá mức cần thiết trên một bài đăng blog, ví dụ, và sẽ chi phí bạn nhiều hơn. Tôi đề nghị tạo một chức năng riêng trong trường hợp đó bằng cách sử dụng phân đoạn: https://firebase.google.com/docs/firestore/solutions/counters

// trigger collections
exports.myFunction = functions.firestore
    .document('{colId}/{docId}')
    .onWrite(async (change: any, context: any) => {
        return runCounter(change, context);
    });

// trigger sub-collections
exports.mySubFunction = functions.firestore
    .document('{colId}/{docId}/{subColId}/{subDocId}')
    .onWrite(async (change: any, context: any) => {
        return runCounter(change, context);
    });

// add change the count
const runCounter = async function (change: any, context: any) {

    const col = context.params.colId;

    const eventsDoc = '_events';
    const countersDoc = '_counters';

    // ignore helper collections
    if (col.startsWith('_')) {
        return null;
    }
    // simplify event types
    const createDoc = change.after.exists && !change.before.exists;
    const updateDoc = change.before.exists && change.after.exists;

    if (updateDoc) {
        return null;
    }
    // check for sub collection
    const isSubCol = context.params.subDocId;

    const parentDoc = `${countersDoc}/${context.params.colId}`;
    const countDoc = isSubCol
        ? `${parentDoc}/${context.params.docId}/${context.params.subColId}`
        : `${parentDoc}`;

    // collection references
    const countRef = db.doc(countDoc);
    const countSnap = await countRef.get();

    // increment size if doc exists
    if (countSnap.exists) {
        // createDoc or deleteDoc
        const n = createDoc ? 1 : -1;
        const i = admin.firestore.FieldValue.increment(n);

        // create event for accurate increment
        const eventRef = db.doc(`${eventsDoc}/${context.eventId}`);

        return db.runTransaction(async (t: any): Promise<any> => {
            const eventSnap = await t.get(eventRef);
            // do nothing if event exists
            if (eventSnap.exists) {
                return null;
            }
            // add event and update size
            await t.update(countRef, { count: i });
            return t.set(eventRef, {
                completed: admin.firestore.FieldValue.serverTimestamp()
            });
        }).catch((e: any) => {
            console.log(e);
        });
        // otherwise count all docs in the collection and add size
    } else {
        const colRef = db.collection(change.after.ref.parent.path);
        return db.runTransaction(async (t: any): Promise<any> => {
            // update size
            const colSnap = await t.get(colRef);
            return t.set(countRef, { count: colSnap.size });
        }).catch((e: any) => {
            console.log(e);
        });;
    }
}

Điều này xử lý các sự kiện, gia tăng và giao dịch. Cái hay ở đây là, nếu bạn không chắc về độ chính xác của tài liệu (có thể trong khi vẫn ở bản beta), bạn có thể xóa bộ đếm để nó tự động thêm chúng vào trình kích hoạt tiếp theo. Có, chi phí này, vì vậy đừng xóa nó đi.

Cùng một loại điều để có được số đếm:

const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts';
const colSnap = await db.doc('_counters/' + collectionPath).get();
const count = colSnap.get('count');

Ngoài ra, bạn có thể muốn tạo một công việc định kỳ (chức năng theo lịch trình) để xóa các sự kiện cũ để tiết kiệm tiền cho việc lưu trữ cơ sở dữ liệu. Bạn cần ít nhất một kế hoạch rực sáng, và có thể có thêm một số cấu hình. Bạn có thể chạy nó vào mỗi chủ nhật lúc 11 giờ tối. https://firebase.google.com/docs/fifts/schedule-fifts

Điều này chưa được kiểm tra , nhưng nên hoạt động với một vài điều chỉnh:

exports.scheduledFunctionCrontab = functions.pubsub.schedule('5 11 * * *')
    .timeZone('America/New_York')
    .onRun(async (context) => {

        // get yesterday
        const yesterday = new Date();
        yesterday.setDate(yesterday.getDate() - 1);

        const eventFilter = db.collection('_events').where('completed', '<=', yesterday);
        const eventFilterSnap = await eventFilter.get();
        eventFilterSnap.forEach(async (doc: any) => {
            await doc.ref.delete();
        });
        return null;
    });

Và cuối cùng, đừng quên bảo vệ các bộ sưu tập trong Firestore.rules :

match /_counters/{document} {
  allow read;
  allow write: if false;
}
match /_events/{document} {
  allow read, write: if false;
}

Cập nhật: Truy vấn

Thêm vào câu trả lời khác của tôi nếu bạn cũng muốn tự động hóa số lượng truy vấn, bạn có thể sử dụng mã được sửa đổi này trong chức năng đám mây của mình:

    if (col === 'posts') {

        // counter reference - user doc ref
        const userRef = after ? after.userDoc : before.userDoc;
        // query reference
        const postsQuery = db.collection('posts').where('userDoc', "==", userRef);
        // add the count - postsCount on userDoc
        await addCount(change, context, postsQuery, userRef, 'postsCount');

    }
    return delEvents();

Nó sẽ tự động cập nhật bài viếtCount trong userDocument. Bạn có thể dễ dàng thêm một số khác vào nhiều số theo cách này. Điều này chỉ cung cấp cho bạn ý tưởng về cách bạn có thể tự động hóa mọi thứ. Tôi cũng cho bạn một cách khác để xóa các sự kiện. Bạn phải đọc mỗi ngày để xóa nó, vì vậy nó sẽ không thực sự giúp bạn xóa chúng sau này, chỉ làm cho chức năng chậm hơn.

/**
 * Adds a counter to a doc
 * @param change - change ref
 * @param context - context ref
 * @param queryRef - the query ref to count
 * @param countRef - the counter document ref
 * @param countName - the name of the counter on the counter document
 */
const addCount = async function (change: any, context: any, 
  queryRef: any, countRef: any, countName: string) {

    // events collection
    const eventsDoc = '_events';

    // simplify event type
    const createDoc = change.after.exists && !change.before.exists;

    // doc references
    const countSnap = await countRef.get();

    // increment size if field exists
    if (countSnap.get(countName)) {
        // createDoc or deleteDoc
        const n = createDoc ? 1 : -1;
        const i = admin.firestore.FieldValue.increment(n);

        // create event for accurate increment
        const eventRef = db.doc(`${eventsDoc}/${context.eventId}`);

        return db.runTransaction(async (t: any): Promise<any> => {
            const eventSnap = await t.get(eventRef);
            // do nothing if event exists
            if (eventSnap.exists) {
                return null;
            }
            // add event and update size
            await t.set(countRef, { [countName]: i }, { merge: true });
            return t.set(eventRef, {
                completed: admin.firestore.FieldValue.serverTimestamp()
            });
        }).catch((e: any) => {
            console.log(e);
        });
        // otherwise count all docs in the collection and add size
    } else {
        return db.runTransaction(async (t: any): Promise<any> => {
            // update size
            const colSnap = await t.get(queryRef);
            return t.set(countRef, { [countName]: colSnap.size }, { merge: true });
        }).catch((e: any) => {
            console.log(e);
        });;
    }
}
/**
 * Deletes events over a day old
 */
const delEvents = async function () {

    // get yesterday
    const yesterday = new Date();
    yesterday.setDate(yesterday.getDate() - 1);

    const eventFilter = db.collection('_events').where('completed', '<=', yesterday);
    const eventFilterSnap = await eventFilter.get();
    eventFilterSnap.forEach(async (doc: any) => {
        await doc.ref.delete();
    });
    return null;
}

Tôi cũng nên cảnh báo bạn rằng các chức năng phổ quát sẽ chạy trên mọi khoảng thời gian cuộc gọi onWrite. Có thể rẻ hơn khi chỉ chạy chức năng trên onCreate và trên các phiên bản onDelete của các bộ sưu tập cụ thể của bạn. Giống như cơ sở dữ liệu noQuery mà chúng tôi đang sử dụng, mã và dữ liệu lặp lại có thể giúp bạn tiết kiệm tiền.


viết một bài báo về nó trên phương tiện để dễ dàng truy cập.
ahmadalibaloch


0

Mất một thời gian để tôi làm việc này dựa trên một số câu trả lời ở trên, vì vậy tôi nghĩ tôi sẽ chia sẻ nó cho người khác sử dụng. Tôi hy vọng nó hữu ích.

'use strict';

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();

exports.countDocumentsChange = functions.firestore.document('library/{categoryId}/documents/{documentId}').onWrite((change, context) => {

    const categoryId = context.params.categoryId;
    const categoryRef = db.collection('library').doc(categoryId)
    let FieldValue = require('firebase-admin').firestore.FieldValue;

    if (!change.before.exists) {

        // new document created : add one to count
        categoryRef.update({numberOfDocs: FieldValue.increment(1)});
        console.log("%s numberOfDocs incremented by 1", categoryId);

    } else if (change.before.exists && change.after.exists) {

        // updating existing document : Do nothing

    } else if (!change.after.exists) {

        // deleting document : subtract one from count
        categoryRef.update({numberOfDocs: FieldValue.increment(-1)});
        console.log("%s numberOfDocs decremented by 1", categoryId);

    }

    return 0;
});

0

Tôi đã thử rất nhiều với các cách tiếp cận khác nhau. Và cuối cùng, tôi cải thiện một trong những phương pháp. Trước tiên, bạn cần tạo một bộ sưu tập riêng biệt và lưu tất cả các sự kiện. Thứ hai, bạn cần tạo một lambda mới để được kích hoạt theo thời gian. Lambda này sẽ đếm các sự kiện trong bộ sưu tập sự kiện và các tài liệu sự kiện rõ ràng. Mã chi tiết trong bài viết. https://medium.com/@ihor.malaniuk/how-to-count-document-in-google-cloud-firestore-b0e65863aeca


Vui lòng bao gồm các chi tiết và mã có liên quan trong chính câu trả lời , việc chỉ cho mọi người vào bài đăng trên blog của bạn không thực sự là điểm của StackOverflow.
DBS

0

Truy vấn này sẽ dẫn đến số lượng tài liệu.

this.db.collection(doc).get().subscribe((data) => {
      count = data.docs.length;
    });

console.log(count)

1
Không phải là một giải pháp tốt vì bạn đang tìm nạp tất cả các tài liệu từ bộ sưu tập mỗi lần. Nó sẽ có giá rất cao. Cách tiếp cận tốt hơn là thiết lập bộ đếm mỗi khi một tài liệu mới được thêm vào bộ sưu tập này để bạn có thể chỉ cần tìm nạp một tài liệu thay vì vài nghìn.
Corentin Houdayer

-1
firebaseFirestore.collection("...").addSnapshotListener(new EventListener<QuerySnapshot>() {
        @Override
        public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {

            int Counter = documentSnapshots.size();

        }
    });

1
Câu trả lời này có thể sử dụng nhiều ngữ cảnh hơn như ví dụ mã.
ShellNinja
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.