MicroLite-ORM / MicroLite

MicroLite ORM framework
microliteorm.wordpress.com
Apache License 2.0
85 stars 24 forks source link

Allow custom instance factories #373

Open Chimaine opened 9 years ago

Chimaine commented 9 years ago

I've been more then once in the situation where I wanted to store or read a class that had one or more read-only properties that needed to be set via the constructor. While we can tell ML to ignore these, the constructor still needs these as arguments.

This can be quite common with libraries (e.g. immutable state information) where you have to write a builder class to be able to read them back from the DB. While this is a perfectly working solution, I would love to have a build-in solution in MicroLite.

An example could be an additional option for Convention Based Mapping or a registration class like TypeConverter.

Thanks for your work on MicroLite!

TrevorPilley commented 9 years ago

It's an interesting idea and not something I'd considered before. At the moment, we essentially do new T() to instantiate a new object before setting the values from the data reader - you can see how that works in the DelegateFactory.

It would introduce more complex logic in the mapping conventions as far as indicating whether a read only property still needs its value sending to the database on insert/update verses a calculated value we don't care about - also I don't think it would be possible to generate the 'instance factory' as we wouldn't know which constructor argument related to which column in the data reader...

I'm currently working on extending the OData support we have in the WebApi extension so it'll be a while before I can commit any time to investigating this further, but feel free to fork the repository and have a play about and see what you think might work!

TrevorPilley commented 9 years ago

@Chimaine would you be expecting the ability to define your own instance factory or just have built in support for a class with constructor params?

e.g.

public class Customer
{
    private readonly string firstName;
    private readonly string lastName;

    public Customer(string firstName, string lastName)
    {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public DateTime DateOfBirth { get; set; }
    public string FirstName { get { return this.firstName; } }
    public string LastName { get { return this.lastName; } }
}

Whereby DateOfBirth, FirstName and LastName are all written to and read from the DB so and you can do:

session.Insert(new Customer("John", "Smith") { DateOfBirth = new DateTime (1976, 7, 22) });

and:

var customer = session.Single<Customer>(1);
Chimaine commented 9 years ago

Sounds good. Requires only a minimum effort to do for the user. The only thing an instance factory could add would be full control over how object are created. Sadly I'll have to wait until Tuesday to confirm that this fits our needs (I don't have access to the code right now).

Chimaine commented 9 years ago

Hello, sorry for the late answer. I didn't have time at work to look at this thoroughly until now.

Yes, this looks like it would fit our needs. How would ML determine what DB fields are mapped to constructor parameters in attribute or convention mapping?

The original idea behind defining your own factory was to account for objects that can only be created through factory methods, like Foo a = Foo.for("Bar");

If it's not too much work, you could provide both methods, whereas using a constructor is the default, but allows overriding.

TrevorPilley commented 9 years ago

No worries, my thinking at the moment is that there will be 2 supported options:

The default constructor option will work exactly as it does in 6.1 with the Attribute and Convention mapping.

If there is a single non default constructor, MicroLite will look at the parameters and if there is a matching public property it will map them as insert only. So taking the example above:

public class Customer
{
    private readonly string firstName;
    private readonly string lastName;

    public Customer(string firstName, string lastName)
    {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public DateTime DateOfBirth { get; set; }
    public string FirstName { get { return this.firstName; } }
    public string LastName { get { return this.lastName; } }
}

FirstName and LastName would both be mapped to the FirstName and LastName columns on the Customers table but they would be only set during an insert and not altered during an update. DateOfBirth would still map to a DateOfBirth column but could be inserted and updated.

In this case, Attribute Mapping would still apply to the property so you could have:

public class Customer
{
    ...
    private readonly string lastName;

    public Customer(..., string lastName)
    {
        ...
        this.lastName = lastName;
    }

    ...
    [Column("FName")]
    public string LastName{ get { return this.lastName; } }
}

Do you think that would work?

Chimaine commented 9 years ago

Yes this would work. Thank you.