Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom serializer and multiple models #106

Closed
codingedgar opened this issue Oct 20, 2020 · 8 comments
Closed

Custom serializer and multiple models #106

codingedgar opened this issue Oct 20, 2020 · 8 comments
Assignees
Labels
enhancement New feature or request

Comments

@codingedgar
Copy link

Hi!

I use couch db database with multiple models from F#, I was wondering:

  1. How to add work with a different serializer (because F# serialized bit weird)?
  2. How to work with different documents in the same db?

I assume I can use diff "databases" that are the same but with different TSource.:

var rebels = client.GetDatabase<Rebel>("StarWars");
var clones = client.GetDatabase<Clone>("StarWars");
@matteobortolazzo
Copy link
Owner

Hi,

So for now there's no option to change the serializer, only Newtonsoft.Json, even if I plan to make it more generic, but it's a ton of work.

About the DB with multiple models, you can do it like that but It will try to deserialize everything anyway.

What you could do is to have a boolean and call the Where method to filter things out.

However, I am open to suggestions for proper implementation.

@panoukos41
Copy link
Contributor

Hey me again I was looking for this feature too I didn't notice it doesn't have it until I saw this but I came up with a simple workaround 😄.
The Important notes are that Find must match the doc type before you run it so that it can be deserialized or else you are trying to deserialize it to the wrong type, anything that runs queries should be abstracted so that the implementation can always return the Where(d => d.Discriminator == discriminator) etc. Actually, the best thing is to have this CouchContet behind an Interface so that you control the API and can be sure Where, Find etc are placed where they must. Haven't tried in scale but it seems this will work well.

// This is dotnet 5.0 Top level statements
var context = new Context();
var empDb = context.Client.GetDatabase<Emperor>();
var rebDb = context.Client.GetDatabase<Rebel>();

for (int i = 0; i < 5; i++)
{
    await empDb.AddAsync(new Emperor
    {
        Id = Path.GetRandomFileName(),
        Underlings = i,
        Alive = i % 2 == 0
    });
    await rebDb.AddAsync(new Rebel
    {
        Id = Path.GetRandomFileName(),
        Name = "Anakin",
        Surname = "Skywalker",
        BattleCount = i.ToString(),
        Enemy = "Obi Wan Kenobi",
        Masters = new() { "Obi", "Sauron" }
    });
}

var rebels = await context
    .Query<Rebel>()
    .ToListAsync();

var emperors = await context
    .Query<Emperor>()
    .ToListAsync();

var rebel = rebDb.FindAsync(rebels[0].Id);
var emperor = rebDb.FindAsync(rebels[0].Id);
WriteLine(rebel is not null);
WriteLine(emperor is not null);
WriteLine();

var jsonOptions = new JsonSerializerSettings { Formatting = Formatting.Indented };

WriteLine($"Rebels count: {rebels.Count}"); // Should be 5.
WriteLine($"Rebels json: \n{JsonConvert.SerializeObject(rebels, jsonOptions)}");
WriteLine();
WriteLine($"Emperors count: {emperors.Count}"); // Should be 5.
WriteLine($"Emperors json: \n{JsonConvert.SerializeObject(emperors, jsonOptions)}");

public class Context : CouchContext
{
    public IQueryable<T> Query<T>() where T : CouchDocument<T>, new()
    {
        string discriminator = new T().Discriminator;
        return Client.GetDatabase<T>().Where(d => d.Discriminator == discriminator);
    }

    protected override void OnConfiguring(CouchOptionsBuilder optionsBuilder)
    {
        optionsBuilder
          .UseEndpoint("http://localhost:5984/")
          .EnsureDatabaseExists()
          .UseBasicAuthentication(username: "admin", password: "password");
    }
}

[JsonObject("multi_database")] // This is the common database name. It could be done on GetDatabase<T>("db_name")
public class CouchDocument<T> : CouchDocument
{
    [DataMember]
    [JsonProperty("discriminator")]
    public virtual string Discriminator => typeof(T).Name;
}

public class Emperor : CouchDocument<Emperor>
{
    public int Underlings { get; set; }

    public bool Alive { get; set; } = true;
}

public class Rebel : CouchDocument<Rebel>
{
    public string Name { get; set; }

    public string Surname { get; set; }

    public string Enemy { get; set; }

    public string BattleCount { get; set; }

    public List<string> Masters { get; set; } = new();
}

@matteobortolazzo
Copy link
Owner

@panoukos41 thanks for the input! As your solution is not generic, I opted to add the filter directly in the query builder. Next step is to update the context

@panoukos41
Copy link
Contributor

Yea it was basically a hack, later I discovered I could skip the context altogether 😀

From my understanding since GetDatabase<TSource>() returns a shallow object an ICouchDatabase and CouchDatabase with no generic argument could be all you need but then every method would have to have a generic argument eg Find<TSource> since it only has to do with what the Flurl request will deserialize to. Then the user could decide what the document will deserialize to when executing the method. This could also open up the possibilities to return the JObject itself if Flurl supports it.

Since an ICouchDatabase<TSource> could be backed by ICouchDatabase so that it wouldn't be a breaking change and even tests would not need to be added it would most likely only require a lot of changes in the existing file 😄

@matteobortolazzo
Copy link
Owner

@panoukos41 doing a non-generic version of GetDatabase is not possible, you need types to call LINQ methods, also because on the method called, the type changes, for example, when you call Select or Average.

What I did instead is:

  • injecting the discriminator as a string at database creation
  • add an internal discriminator field in CouchDocument
  • set the field on document creation
  • add a Where(d => d.Discriminator == Value) to the LINQ call
  • add configuration support in the context so that:
    • the discriminator is set only if multiple CouchDocuments target the same database
    • the value of the discriminator is the name of the type

This way everything is automatic and safe.

Everything is in the dev branch if you want to test and give me your feedback
couchdb-net/tree/dev#database-splitting

@panoukos41
Copy link
Contributor

I see, yea I didn't really know how LINQ works :P but I am happy this feature now works out of the box 😄

@matteobortolazzo
Copy link
Owner

V3.0.0 released

@codingedgar
Copy link
Author

Thank you!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants