Design Meeting Notes - February 14, 2013

Branching for EF releases

So far since the move to Git we haven’t done any branching for releases. For the next pre-release drop we will start doing so.

Branch structure

Options

  1. Main branch is stable, we work in feature/local branch(s) and merge into master (DevDiv Model)
  2. We create a release branch (i.e. EF6), check into master and then merge desired changes into feature branch (EF5 Model)

Advantages

Option 1:

  • Main branch is always stable

Option 2:

  • Only need to merge changes that need porting into current release
  • Main branch is always the latest changes
  • Nightly build always taken from one place
  • Simple to groc branch structure

The second option has worked well for us in the past, so we will continue with this approach for EF6

Nightly builds

Agreed plan:

  • Continue to build, sign, release master (latest changes)
    • This means that the nightly builds will continue to be the hottest signed bits
  • Setup a second nightly build/sign for current release
    • Allows use of the build/sign infrastructure for creating the actual release packages
    • We will likely make this public as well so that as we approach a release people can get a signed nightly build that may be more stable that the normal nightlies

Pull request: extending Migrations

Continuing discussion on pull request: http://entityframework.codeplex.com/SourceControl/network/forks/iceclow/efmigrationsextensions2/contribution/4064

Following feedback from last week, the request was updated with the suggested interface:

public interface IAddMigrationOperation
{
    void AddOperation(MigrationOperation migrationOperation);
}

The team discussed possible names for this interface and while none of the options seemed great, there was general approval for IDbMigration. This aligns with the class name and is also a more general name that could be used for other members in the future, assuming the breaking change is acceptable.

Question: how should our default SQL provider handle unknown operations?

Three options are:

  • Throw
  • Ignore
  • Generate T-SQL warnings

Current code ignores, but in general it seems like this would could cause important migrations to be silently missed. It seems like it will almost always be an error to have a custom migration and use it with a provider that doesn’t understand that operation. Therefore the best choice seems to be to throw. It is easy to subclass the provider and have it ignore the custom migration if this is desired, and if we find a better default then we can stop throwing in the future.

Code First: modification function mapping API

Top-level method name

As previously discussed, the top-level by-convention mapping is triggered by a method call, but should the method really be MapToFunctions?

  • Function mapping is an EDM term more than an industry term
  • People tend to talk about “stored procedure” mapping, so we could use this term, but it is long:
modelBuilder.Entity<Engine>().MapToStoredProcedures();

modelBuilder
    .Entity<Tag>()
    .HasMany(t => t.Products)
    .WithMany(p => p.Tags)
    ().MapToStoredProcedures();

Decision: Use MapToStoredProcedures—it is long but provides the best discoverability and seems to match the way people talk about this.

Function/Parameter name customization

Initial proposal:

modelBuilder
    .Entity<OrderLine>()
    .MapToStoredProcedures(
        map =>
        {
            map.InsertFunction(f => f.HasName("insert_order_line"));
            map.UpdateFunction(f => f.HasName("update_order_line", "foo"));
            map.DeleteFunction(f => f.HasName("delete_order_line", "bar"));
        });

modelBuilder
    .Entity<Building>()
    .MapToStoredProcedures(
        map =>
        {
            map.InsertFunction(f => f.Parameter(b => b.Address.Line1).HasName("ins_line1"));
            map.UpdateFunction(f => f.Parameter(b => b.Id).HasName("upd_id"));
            map.DeleteFunction(f => f.Parameter(b => b.Id).HasName("del_id"));
        });

Questions:

  • What should the Insert/Update/Delete names be?
    • Having Function here doesn’t really add much, and doesn’t match MapToStoredProcedures
    • Decision: just cut Function from the names
  • The only thing that can be done with a parameter is to change its name
    • Everything else is governed by the model
    • Given this, is it worth having a Parameter call followed by a HasName call rather than just having one method?
    • The advantages of Parameter/HasName are that it is more consistent with the other fluent APs, and is generally “more fluent”
    • The advantages of a single method is that it’s less API surface/code, and the method can return the function configuration allowing multiple calls to be chained easily
    • Decision: go with a single method—the chaining advantage is very nice
  • Given we are going with a single method, should this be called Parameter or <SomeVerb>Parameter—for example NameParameter?
    • Decision: go with just Parameter for now
    • The verb is useful if it’s not clear why the call is needed, but this seems fairly clear anyway

Updated proposal:

modelBuilder
    .Entity<OrderLine>()
    .MapToStoredProcedures(
        map =>
        {
            map.Insert(f => f.HasName("insert_order_line"));
            map.Update(f => f.HasName("update_order_line", "foo"));
            map.Delete(f => f.HasName("delete_order_line", "bar"));
        });

modelBuilder
    .Entity<Building>()
    .MapToStoredProcedures(
        map =>
        {
            map.Insert(f => f.Parameter(b => b.Address.Line1, "ins_line1"));
            map.Update(f => f.Parameter(b => b.Id, "upd_id"));
            map.Delete(f => f.Parameter(b => b.Id, "del_id"));
        });

Original value parameter name customization

In most cases a property maps to a single parameter that is used only for current values or only for original values. However, when an entity has a non-store computed concurrency token, then the stored proc must have two parameters—one for original values and one for current values. In such a case either or both of these parameters may need to be named. The original proposal for this is:

modelBuilder
    .Entity<Engine>()
    .MapToStoredProcedures(
        map => map.Update(
            f =>
            {
                f.Parameter(e => e.Name).HasName("name_cur");
                f.Parameter(e => e.Name, originalValue: true).HasName("name_orig");
            }));

This uses two overloads—one with the bool, one without. If the one without is used then:

  • If there is only a single parameter (current or original), then it is named
  • If there are two parameters, then the one for current is named

If the overload with the bool is used, then the parameter to name is based on the value of the bool.

Brainstorming led down several paths an ultimately came down to two overloads, one which takes a single string, one which takes two strings. If the single string is used then the behavior is as described above. If two strings are used, then the two strings are explicitly for current and original:

modelBuilder
    .Entity<Engine>()
    .MapToStoredProcedures(
        map => map.Update(
            f =>
            {
                f.Parameter(e => e.Name, "name_cur", "name_orig");
            }));

Result binding

Based on previous patterns/naming, result binding will look like this:

modelBuilder
    .Entity<Order>()
    .MapToStoredProcedures(
        map =>
        {
            map.Insert(f => f.Result(o => o.OrderId, "order_id"));
            map.Update(f => f.Result(o => o.Version, "timestamp"));
        });

Rows affected parameters

Based on previous patterns/naming, naming the “rows affected” parameters will look like this:

modelBuilder
    .Entity<Order>()
    .MapToStoredProcedures(
        map =>
        {
            map.Update(f => f.RowsAffectedParameter("rows_affected1"));
            map.Delete(f => f.RowsAffectedParameter("rows_affected2"));
        });

Many-to-many associations

Based on previous patterns/naming, many-to-many mapping will look like this:

modelBuilder
    .Entity<Tag>()
    .HasMany(t => t.Products)
    .WithMany(p => p.Tags)
    .MapToStoredProcedures(
        map =>
        {
            map.Insert(
            f =>
            {
                f.HasName("ins_product_tag");
                f.LeftKeyParameter(t => t.Id, "tag_id");
                f.RightKeyParameter(p => p.Id, "product_id");
            });
            map.Delete(
            f =>
            {
                f.HasName("del_product_tag", "bar");
                f.LeftKeyParameter(t => t.Id, "tag_id");
                f.RightKeyParameter(p => p.Id, "product_id");
            });
        });

The use of primary key properties in this API helps make “left” and “right” keys easier to understand. We should consider allowing this pattern in other many-to-many mapping APIs. CodePlex issue 873: http://entityframework.codeplex.com/workitem/873

Independent associations that are not many-to-many still need to be addressed.

Last edited Feb 14, 2013 at 11:02 PM by ajcvickers, version 2

Comments

No comments yet.