redis / redis-om-dotnet

Object mapping, and more, for Redis and .NET
MIT License
447 stars 76 forks source link

Fluent API instead of attributes #196

Open iozcelik opened 1 year ago

iozcelik commented 1 year ago

When writing entities, I do not prefer to use attributes too much. After release of ef core fluent API, I prefer it. And not only me, lots of .net developer prefer same.

I advice to redis om .net adds an alternative usage for fluent api.

Cuurent usage:

var provider = new RedisConnectionProvider("redis://localhost:6379");
var connection = provider.Connection;
var people = provider.RedisCollection<Person>();
connection.CreateIndex(typeof(Person));

[Document(StorageType = StorageType.Json, IndexName = "person-idx")]
public class Person {
    [RedisIdField]
    [Indexed]
    public string Id { get; set; }
    [Searchable]
    public string Name { get; set; }

    [Indexed(Sortable = true)]
    public int? Age { get; set; }

    [Indexed(Sortable = true)]
    public double? Height { get; set; }

    [Indexed(Sortable = true)]
    public int? DepartmentNumber { get; set; }

    [Indexed(Sortable = true)]
    public double? Sales { get; set; }

    [Indexed(Sortable = true)]
    public double? SalesAdjustment { get; set; }
}

I prefer something like that:

public class PersonEntityTypeConfiguration : IRedisCollectionConfiguration<Person> {
    public void Configure(RedisCollectionTypeBuilder<Person> builder) {
        builder.StorageType(StorageType.Json);
        builder.Property(b => b.Id).IsIndexed().IsRedisIdField();
        builder.Property(b => b.Name).IsSearchable();
        builder.Property(b => b.Age).IsIndexed(sortable:true);
        builder.Property(b => b.Height).IsIndexed(sortable:true);
        builder.Property(b => b.DepartmentNumber).IsIndexed(sortable:true);
        builder.Property(b => b.Sales).IsIndexed(sortable:true);
        builder.Property(b => b.SalesAdjustment).IsIndexed(sortable:true);
        builder.HasIndex(typeof(Person), "person-idx");
    }
}

var provider = new RedisConnectionProvider("redis://localhost:6379");
var connection = provider.Connection;

provider.AddRedisCollectionConfiguration<PersonEntityTypeConfiguration>();

var people = provider.RedisCollection<Person>();

public class Person {
    public string Id { get; set; }
    public string Name { get; set; }
    public int? Age { get; set; }
    public double? Height { get; set; }
    public int? DepartmentNumber { get; set; }
    public double? Sales { get; set; }
    public double? SalesAdjustment { get; set; }
}
slorello89 commented 1 year ago

Hi @iozcelik - that's a pretty radical departure from how the library was built and would be a really substantial amount of effort (rebuilding the expression parsing logic, the index serialization logic, basically every test would need to be duplicated, reworking all the docs, etc. . .). There are both gains and drawbacks to both approaches, they are probably about equivalent on balance, with the primary differentiator most likely being a matter of taste, so I'm not sure I could justify the time required to build this.

iozcelik commented 1 year ago

Hi @slorello89 , I design a new library and it has both version lite and normal. Lite version has not redis support, it uses .net memory cache and it is enough because it has single node.

However, normal version is distibuted system. Uses same classes with lite version. So in this case, I use redis. So for this basic scenario (I have complex scenarios also :)), fluent api better than attributes. I add necessary packages only if required.

I try to handle of course with my way. But native support could be better.

VagyokC4 commented 1 year ago

Hi @iozcelik, One thing you can try is a feature I use within Service Stack Framework - Dynamically adding Attributes

typeof(MyPoco)
    .AddAttributes(new DataContractAttribute())
    .GetProperty(nameof(MyPoco.LastName))
    .AddAttributes(new DataMemberAttribute { Name = "Surname" });

Possibly you could use something like this to wire up the attributes in a Builder type method that gives you what you need?

iozcelik commented 1 year ago

Hi @VagyokC4 , yes probably I use like this one. Also, probably, I write a wrapper for redis :)

frostshoxx commented 1 year ago

That new proposed format reminds me of how HotChocolate (.NET library for GraphQL) uses for defining GraphQL object type

MarkEwer commented 8 months ago

+1 for this. I have an existing app that I would where like to build a new persistence provider that uses Redis OM but I can't add attributes (or the Nuget dependency) to the existing core product. I would like to isolate it into the persistence provider assembly.

The only way I can find to do that now is to map all of the existing domain objects into new DTOs for persistence.

soroshsabz commented 5 months ago

ITNOA

Hi,

@slorello89

thanks

slorello89 commented 5 months ago

Hi @soroshsabz, IMO there's pretty robust and consistent demand for this feature, so it's something I'd like to do. I haven't started developing it, but I'd like it to look somewhat similar to the fluent api that EF provides for model building. It's a major undertaking though and I don't currently have a timeline.

soroshsabz commented 5 months ago

So do you accept PR for this?

AhmadAlMunajjed commented 2 months ago

+1 for this for helping dotnet community writing cleaner code

Using attributes forces developers to add external services dependency to domain core project Fluent API design promotes loose coupling and separation of concerns between the domain and infrastructure layers.

AhmadAlMunajjed commented 2 months ago

Any plans to implement it in the near future?

We have no option but to add redis-om-dotnet Nuget dependency to our domain for now. We really appreciate considering this feature.

To achieve the separation of concerns between the domain and infrastructure layers, in additional to Fluent Api, we need the Async methods to be written as extension methods to Iqueryable instead of implemeting it for IRedisCollection