f-miyu / Plugin.CloudFirestore

MIT License
121 stars 44 forks source link

Create Collection inside document (like on the Database GUI) #19

Closed Loki606 closed 4 years ago

Loki606 commented 4 years ago

Is there any way to create new collections using this library?

ElishaMisoi commented 4 years ago

Hello @Loki606, please give more information to this question. What are you trying to achieve?

Loki606 commented 4 years ago

I have a nested collection within a document like it can be done on the GUI on Firebase Console. I want to create this from code.

Loki606 commented 4 years ago

/Bussinesses/3cQw62BZjgvEGhdxJxsJ/test/WJ7wq22xjpeZP2VJfpOF .. like this. In this case the nested collection is "test" and there are two documents referenced. Also it would be good to know if I can create a collection from code. like "CrossCloudFirestore.Current.Instance.CreateCollection".

Loki606 commented 4 years ago

Basically the core problem I'm trying to resolve with this architecture is that within each document I have Dictionary<DateTime, List{CustomClass}> variable and when I update it I have to update with just one CustomClass and I don't want to risk overwriting data. Perhaps there is another way of updating just one CustomClass item with the UpdateAsync? Can you help?

Loki606 commented 4 years ago

Also both my "Bussiness" class and "CustomClass" are mapped, but I'm not sure how to make a reference of one to the other using this library.

ElishaMisoi commented 4 years ago

Yeah I think I have an idea of what you're trying to achieve. So basically you're trying to update a single 'customClass' in a list that is within a Dictionary in a document? Is that right?

Loki606 commented 4 years ago

Wihtin a List that is within a Dictionary :S ( i.e. Dictionary<DateTime, new List{CustomClass}>() )

ElishaMisoi commented 4 years ago

Wow... Okay, at this point there is no way of getting to that single 'customClass' within that list. As there is no way to reference it. You can only update the entire list since you can reference the dictionary that holds the list.

Loki606 commented 4 years ago

is there any way of creating a collection like I mentioned?

ElishaMisoi commented 4 years ago

Yes you can do that... We already know that the document is 'Business', collection is 'randomID' then now you can add document 'test' with collection 'randomID'.

But you'll first have to know the 'randomIDs'. Makes sense?

ElishaMisoi commented 4 years ago

Check out this thread for generating random IDs... unfortunately, there is no 'out of the box' way of generating IDs with this library.

Loki606 commented 4 years ago

I'm sorry, I don't really understand. What I want to do is to create an all new collection and specify and ID for it. The path to the newly created collection being: "Collection1ID\AutoGenDocumentID\Collection2ID"

Loki606 commented 4 years ago

image

See the path here, I am created a new collection within a document on the GUI, but is there a way to do this from code, Create a New Collection (not document)?

Loki606 commented 4 years ago

so the whole thing looks like this:

image

ElishaMisoi commented 4 years ago

Yes, you can do that, but you'll have to manually generate the IDs. Give me a few minutes a write some code on how you can do that.

ElishaMisoi commented 4 years ago
   `public class MyObject
    {
        public string Name { get; set; }
        public string ID { get; set; }
    }

    string FirebaseID()
    {
        return CrossCloudFirestore.Current
                     .Instance
                     .GetCollection("randomPath")
                     .CreateDocument().Id;
    }

    async Task SET_DOCUMENT(dynamic obj, string documentPath)
    {
        try
        {
            await CrossCloudFirestore.Current
                     .Instance
                     .GetDocument(documentPath)
                     .SetDataAsync(obj);
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
        }
    }

    async Task NestedCollection()
    {
        try
        {
            string firstID = FirebaseID();

            await SET_DOCUMENT(new MyObject { Name = "test name", ID = "some ID" }, $"Business/{firstID}");

            string secondID = FirebaseID();

            await SET_DOCUMENT(new MyObject { Name = "test name 2", ID = "some ID 2" }, $"Business/{firstID}/test/{secondID}");
        }
        catch(Exception ex)
        {
            Debug.WriteLine(ex.Message);
        }
    }`
Loki606 commented 4 years ago

That's amazing! Thank you!

ElishaMisoi commented 4 years ago

My advice is, avoid nesting as much as possible, not unless it's absolutely necessary and it won't affect your app's performance.

Loki606 commented 4 years ago

Well there would be another option as to create a whole new Collection for this CustomClass and reference it for every bussiness via it's ID. But like that my queries would look like this:

foreach(string s in Bussines.ListCustomClass){ CrossCloudFirestore.Current.Instance .GetCollection("CustomClass") .GetDocument($"{s}") .GetDocumentAsync(); }

So I imagine that the nested collection would be less taxing.

ElishaMisoi commented 4 years ago

Instead of nesting, you could probably just place the list in a different document. For instance, now that we have 'Business/firstID', just place the list in a different document, 'BusinessLists/custom-classes/firstID/secondID' In this case, you can still retrieve the business lists of your business without nesting. So when retrieving your lists, you get your businessID and retrieve it's lists from the BusinessLists document. Yeah the path looks nasty I know, but it's because Firestore only accepts even numbers for references.

ElishaMisoi commented 4 years ago

This will save you a lot of performance issues for instance when scaling up or integrating Firebase Functions.

Loki606 commented 4 years ago

I would also need to sometimes itterate through the whole collection to get a bussiness by using the filter option so having documents with different classes may cause failures. Or can't it?

ElishaMisoi commented 4 years ago

I can't see the bigger picture because I don't know what the collection holds. But if the collection holds different objects, I believe somewhere you have the BusinessID so if you can get that ID then you can get the Business. I'm I getting it right...?

ElishaMisoi commented 4 years ago

In your 'customClass' add a 'BusinessID' field, which will be the ID of the Business. Since you can already generate the ID then you can add it to a 'customClass'.

Loki606 commented 4 years ago

Yes I actually do have a field in the CustomClass to reference the bussiness, thing is I have a trio of base elements that need to be interconnected. Users, Businesses and Appointments. Each of these will be referencing the other two.

Loki606 commented 4 years ago

I was trying to nest the Appointments in Businesses so then I don't have to query firebase two times, and I also split the appointments into separate days which makes it a lot easier for me to search for an empty time interval during a specific date. The problems only start coming when I have to update individual appointments, because I believe updating the entire Dictionary field could result in data loss, but this solves it.

ElishaMisoi commented 4 years ago

Glad to help. But try as much as possible not to nest your data. Think of it as an SQL database. You can separate your data into different documents and query twice for now. Then later on write a Firebase function that you can only call once then persist data using a third party library.

All the best in your app 👍

Loki606 commented 4 years ago

I'm getting an error in the "SET_DOCUMENT" function for this line:

                await CrossCloudFirestore.Current
                     .Instance
                     .GetDocument(Path)
                     .SetDataAsync(obj);

Missing compiler required member 'Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create'

ElishaMisoi commented 4 years ago

Replace dynamic with your object.

The reason why I used dynamic was to ignore empty fields in an object. The library does not ignore empty fields so you end up with empty fields in your DB. So I ended up converting to a JSON object first ignoring empty fields.

If you still want to use dynamic, you'll have to do this:

   `public async Task SET_DOCUMENT(dynamic obj, string documentPath)
    {
        try
        {
            await CrossCloudFirestore.Current
                     .Instance
                     .GetDocument(documentPath)
                     .SetDataAsync(FireObject(obj));
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
        }
    }

    IDictionary<string, object> FireObject(dynamic obj)
    {
        string ignored = JsonConvert.SerializeObject(obj, Formatting.Indented,
        new JsonSerializerSettings
        {
            Formatting = Formatting.Indented,
            NullValueHandling = NullValueHandling.Ignore,
            DefaultValueHandling = DefaultValueHandling.Ignore,
            ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
        });

        return JsonConvert.DeserializeObject<IDictionary<string, object>>(ignored);
    }`

If you encounter any more errors just replace dynamic with your object. I understand that using this approach will throw an error when dealing with some collections.

Loki606 commented 4 years ago

It's fine, I downloaded a nuGet package for Microsoft.CSharp and now it works! I used you code to create regular Collections for every Business account (not nested) and add a reference to it in the Business Document :D I'm very happy with the results here and thank you SO much for your help. How can I replay you?

image

ElishaMisoi commented 4 years ago

Aaah... Awesome 👍 Haha... There's no need to repay me. I am yet to meet a stingy developer, collaboration is all we gat in these streets 😂😂😂.

Loki606 commented 4 years ago

Just as an FYI for me, is there a way to update an individual item in a List of strings in a documents? I mean how would I go about the path there?