Поделиться через


Code Only Enhancements

We've been working hard on Code Only since the first preview.

In the next release you will be able to specify:

  1. Navigation Property Inverses.
  2. Property Facets, like Nullability, MaxLength, Precision etc.
  3. Property to Column mappings
  4. Type to Table(s) mappings
  5. Inheritance strategy
  6. Encapsulate configuration

The rest of this post will drill into each of these features in turn.

Registering NavigationProperty inverses:

You can now Register Inverses, i.e. link one navigation property to another, like this:

builder.RegisterInverse(
(Customer c) => c.Orders,
(Order o) => o.Customer)
);

This code indicates that Customer.Orders is the other end of the Order.Customer relationship. Adding order1 to the customer1.Orders collection, has the same effect as setting the order1.Customer to customer1.

Specifying Property Facets:

You can also specify property facets, i.e. things like Nullability, MaxLength, Precision etc, like this:

var customerConfig = new EntityConfiguration<Customer>();
// We can infer that the ID is the Primary Key,
// but not that it is generated in the database on insert.
customerConfig.ForProperty(c => c.ID)
.Identity();
customerConfig.ForProperty(c => c.Name)
.MaxLength(100)
.NonUnicode();
customerConfig.ForProperty(c => c.Website)
.MaxLength(200)
.Nullable()

builder.Configure(customerConfig);

This configures the Customer type so that:

  • The ID property is an Identity column, i.e. the value is computed by the database when we do an insert.
  • The Name property has a MaxLength of 100 characters and is NonUnicode i.e. in SqlServer VARCHAR rather than NVARCHAR.
  • The Website property has a MaxLength of 200 characters and is Nullable.

These facets target the Conceptual Model (i.e. CSDL), and from there flow to the database too (i.e. SSDL).

Encapsulating Facet Configuration

You can create a class to encapsulate all this configuration by deriving from EntityConfiguration<T>.

For example:

public class CustomerConfig: EntityConfiguration<Customer>
{
public CustomerConfig(){
ForProperty(c => c.ID)
.Identity();
ForProperty(c => c.Name)
.MaxLength(100)
.NonUnicode();
ForProperty(c => c.Website)
.MaxLenght(200)
.Nullable();
}
}

We recommend creating classes like this instead of configuring an EntityConfiguration<> because of the encapsulation benefits.

Specifying the Tablename

When you use Configure<T>(..) the EF infers a default mapping, inheritance strategy (TPH) and table name(s) for you.

But if you want to specify the table name you can do this:

var customerConfig = new EntityConfiguration<Customer>();
// configure the facets, as per the above example
...

// register configuration with a particular tablename
builder.Tables[“dbo.Custs”] = customerConfig;

Specifying Mappings:

If you need more control over the mappings (for example to map to an existing database or use corporate naming policies) then you can specify mappings like this:

EntityMap<Customer> customerMap =
Map.OfType<Customer>(
c => new {
cid = c.ID,
c.Name,
csite = c.Website
}
);

Interpreting a Mapping

This mapping states that the ID property is mapped to the ‘cid’ column, the Name property is stored in the ‘Name’ column and the Website property is mapped to the ‘csite’ column.

Properties not referenced are not persisted (just like properties in a partial class when using the default code generation of EF)

LINQ Comprehension Syntax

You can even specify exactly the same thing using a LINQ comprehension syntax too:

EntityMap<Customer> customerMap =
from c in Map.OfType<Customer>()
select new {
cid = c.ID,
c.Name,
csite = c.Website
};

Specifying Facets with Mapping

Once you’ve configured the mapping you can also specify facets on the map like this:

customerMap.ForProperty(c => c.ID)
.Identity();
customerMap.ForProperty(c => c.Name)
.MaxLength(100)
.NonUnicode();
customerMap.ForProperty(c => c.Website)
.MaxLenght(200)
.Nullable();

Specifying the table

The final step is to assign the map to a table.

builder.Tables[“dbo.Custs”] = customerMap;

Now we’ve specified a custom table, mapping and custom facets for our Customer class.

Specifying Inheritance:

The default inheritance strategy used by CodeOnly is Table Per Hierarchy (or TPH).

If however you need a different strategy you need to dive in and configure the mappings.

Imagine if you have three classes you need to map: Vehicle , Car and Boat where both Car and Boat are derived from Vehicle which is abstract.

clip_image001

Table Per Hierarchy (TPH)

If you want to map this using TPH, you could do it like this:

var vehicleMap =
Map.OfTypeOnly<Vehicle>(
v => new {
vid = v.ID,
v.Name,
vdesc = v.Description
v.MaxPassengers,
}
).Union(Map.OfTypeOnly<Car>(
c => new {
            vid = c.ID,
c.Name,
vdesc = c.Description
c.MaxPassengers,
trans = c.Transmission,
tspd = c.Topspeed,
ccty = c.EngineCapacity,
ncyld = c.NoCylinder,
discriminator = “CAR”
})
).Union(Map.OfTypeOnly<Boat>(
b => new {
            vid = b.ID,
b.Name,
vdesc = b.Description
b.MaxPassengers,
lng = b.Length,
b.HasSail,
b.HasEngine
discriminator = “BOAT”
})
);

builder.Tables[“dbo.vehicles”] = vehicleMap;

In a TPH mapping:

  1. OfTypeOnly() is used to create mapping fragments.
  2. Mapping fragments are then unioned so they can be assigned to one table (TPH uses a single table for the whole hierarchy).
  3. A discriminator column is required for each non-abstract type. The discriminator column can have any name and must have a different constant value (i.e. “CAR”) for each non abstract type in the hierarchy.
  4. When mapping a derived type you must re-map all properties mapped in the base-type(s).

Table Per Type (TPT)

If you want to map the same hierarchy using Table Per Type or TPT, this is how you do it:

builder.Table[“dbo.Vehicles”]=
Map.OfType<Vehicle>(
v => new {
vid = v.ID,
v.Name,
vdesc = v.Description
v.MaxPassengers,
}
);

builder.Tables[“dbo.Cars”] =
Map.OfType<Car>(
c => new {
cid = c.ID,
trans = c.Transmission,
tspd = c.Topspeed,
ccty = c.EngineCapacity,
ncyld = c.NoCylinders,
}
);

builder.Tables[“dbo.Boats”] =
Map.OfType<Boat>(
b => new {
bid = b.ID,
lng = b.Length,
b.HasSail,
b.HasEngine
}
);

In a TPT mapping:

  1. OfType<>() is used in each mapping 'fragment'
  2. Each mapping 'fragment' is assigned to a different table.
  3. Each mapping 'fragment' only maps properties declared on the current type, except…
  4. Key properties must be mapped in every fragment (this allows the tables participating in the type to be joined).
  5. There are no discriminators

Table Per Class (TPC)

You can also map this hierarchy using TPC like this:

builder.Tables[“dbo.Cars”] =
Map.OfTypeOnly<Car>(
c => new {
cid = c.ID,
c.Name,
vdesc = c.Description
c.MaxPassengers,
trans = c.Transmission,
tspd = c.Topspeed,
ccty = c.EngineCapacity,
ncyld = c.NoCylinders,
}
);

builder.Tables[“dbo.Boats”] =
Map.OfTypeOnly<Boat>(
b => new {
bid = b.ID,
b.Name,
description = b.Description
b.MaxPassengers,
lng = b.Length,
b.HasSail,
b.HasEngine
}
);

In a TPC Mapping:

  1. Like TPH we use OfTypeOnly(..)
  2. But unlike TPH each non-abstract type has a its own table. (So there is no table for Vehicles because it is abstract).
  3. Each mapping fragment re-maps every, non-transient, property.
  4. There is no mapping for any abstract classes in the hierarchy.
  5. There are no discriminators

Default Foreign Key Locations:

If we see a reference (i.e. Order.Customer) we assume the multiplicity is 0..1. Meaning the the Foreign Key or FK is nullable.

If we see a collection (i.e. Customer.Orders) we assume the multiplicity is many.

Then when you Register Inverses, we know the multiplicity of both ends of the relationships i.e. in the above example we know that there are 0..1 Customers for an Order and many Orders for a Customer.

So by convention there are 3 main types of relationships, we need to infer the FK location for:

0..1 to many –> by convention we put the FK on the many end. So in the Customer.Orders example the FK is put in the Orders table. 

many to many –> there is no option but to introduce a join table

0..1 to 0..1 –> You can configure where the FK lives, but in the absence of configuration we will introduce a join table.

Sometimes however a reference is not 0..1 it is 1, i.e. the FK, wherever it might be, may not be nullable.

This is how you specify that the FK is non-nullable:

var orderConfig = builder.Configure<Order>();
orderConfig.RegisterInverse(o => o.Customer, c => c.Orders);
orderConfig.ForProperty(o => o.Customer).NonNullable();

This tells us that there is exactly 1 Customer per Order and many Orders for a Customer.

The ability to tell us that a reference is NonNullable introduces several new multiplicity combinations, for which we need conventions too:

1 to many –> by convention we put the FK on the many end, and make it non-nullable in the database.

0..1 to 1 –> by convention we put the FK on the 1 end, and make it non-nullable.

1 to 1 –> like 0..1 to 0..1 we can’t decide where to put the FK so by convention we introduce a join table.

Specifying FK Mappings:

So far we’ve created mappings for the properties of entities.

What about Navigation Properties and FKs?

All types of relationships (except many to many) can be modeled without a join table in the database. So we allow you to map FKs as part of an EntityMap like this:

EntityMap<Customer> customerMap =
from c in Map.OfType<Customer>()
select new {
cid = c.ID,
c.Name,
csite = c.Website,
salesPersonFK = c.SalesPerson.ID
};

customerMap.RegisterInverse(c => c.SalesPerson, s => c.Clients);
builder.Tables[“dbo.Custs”] = customerMap;

This specifies that the Customer.SalesPerson navigation property, and it’s inverse SalesPerson.Customers, are stored in the salesPersonFK column in the dbo.Custs table. Because the fragment maps the salesPersonFK column to the c.SalesPerson.ID, where SalesPerson.ID is the Primary Key (or part of the PrimaryKey) of the related SalesPerson entity, and of course FK’s point to PK’s.

Specifying Join Table Mappings:

In the case of many to many relationships you must have a join table, so absent mapping information we produce a join table by convention.

If however you need more control you can do this:

var blogPostsMap = new AssociationMap<Blog, Post>(
b => b.Posts
).Map(
(b, p) => new {BlogId = b.ID, PostId = p.ID}
);

builder.Tables[“dbo.BlogPosts”] = blogPostsMap;

This says the many to many relationships between Blog and Post is stored in the ‘dbo.BlogPosts’ table which has two columns:

  • ‘BlogId’ which is also an FK pointing to the column to which the ID property of Blog is mapped.
  • ‘PostId’ which is also an FK pointing to the column to which the ID property of Post is mapped.

Entity Splitting:

Code Only can even support more advanced mapping strategies like entity splitting:

builder.Tables[“dbo.Customer”] = Map.OfType<Customer>(
c => new {
cid = c.ID,
c.Name,
active = c.IsActive
}
);

builder.Tables[“dbo.CustomerDetails”] = Map.OfType<Customer>(
c => new {
cid = c.ID,
c.Size,
c.Industry
}
);

This says that the Customer entity is split across the dbo.Customer and dbo.CustomerDetails tables.

Encapsulating all the Configuration:

You can also write a class that derives from EntityMap<T>, to hold all the mapping, facets etc. For example here a class that holds the configuration for Product

public class ProductMap: EntityMap<Product>
{
public ProductMap{
this.Map( p => new {
pid = p.ID,
pcode = p.Name,
cid = p.Category.ID
});
this.ForProperty(p => p.ID).Identity();
this.ForProperty(p => p.Name).MaxLength(100)
.NonUnicode();
this.ForProperty(p => p.Category).NonNullable();
this.RegisterInverse(p => p.Category,
c => c.Products);
}
}

This approach is highly recommended, because configuring the Product type is now trivial:

builder.Tables[“dbo.Products”] = new ProductMap();

Summary:

As you can see we are planning a lot of enhancements which will allow most core scenarios.

What do you think? Do you like the API? Are there things you’d like to see look a little different?

As always we are very keen to hear your feedback.

Alex James
Program Manager, Entity Framework Team, Microsoft.

This post is part of the transparent design exercise in the Entity Framework Team. To understand how it works and how your feedback will be used please look at this post .

Comments

  • Anonymous
    August 03, 2009
    Honestly I love it! This is the type of configuring my entities I like to see. Compile time validation is awesome. The only thing really important to me is to be able to make the db experts happy with lower_case_under_score column names etc :) Good work so far!

  • Anonymous
    August 03, 2009
    Seems very much inspired from Fluent Nhibernate. Good work anyway, when it will be available?

  • Anonymous
    August 03, 2009
    Part of the the BizSpark initiative.  Implementing on EF and tested your EF CTP with a colleague today - we immediately ran into the issue of many to many's - in a simple and complex scenario we still need the inverses. I think the API proposed is great - when it's available in a CTP (hopefully the next, and soon?) we'll be able to write more robust apps to flex this new muscle.  It puts the RAD speed on par with Ruby's, and with the CrateDatabaseScript() method we can build our own migration tools. Exciting technology; MSFT is getting agile.  Keep up the great work team.

  • Anonymous
    August 03, 2009
    @Kazimanzurrashid, We don't have any dates yet we are still working through logistics. Alex

  • Anonymous
    August 03, 2009
    I have some serious concerns about the fact that so much of the configuration is done in code. Much of the configuration could be done as it is done in the .net RIA services by the use of attributes either directly in the class or using a separate metadata class. This makes it substantially cleaner and clearer to read, and keeps the information where it should be - associated with the class and not in some other odd place.

  • Anonymous
    August 03, 2009
    I must say I'm not a fan of those overly fluent interfaces, I’m a fan of keeping things simple. Did you discuss this API with the Framework Design Guideline guys (Brad & Krzysztof)? Why not write the code like this: var id = customerMap.GetProperty(c => c.ID); id.IsIdentity = true; var name = customerMap.GetProperty(c => c.Name); name.MaxLength = 100; name.IsUnicode = false; var website = customerMap.GetProperty(c => c.Website); website.MaxLength = 200; website.IsNullable = true;

  • Anonymous
    August 03, 2009
    Fluent NHibernate for EF, nice stuff.

  • Anonymous
    August 03, 2009
    Interesting, takes a slightly different approach to Fluent NHibernate.

  • Anonymous
    August 03, 2009
    I believe Stefan raises an interesting point - attribute driven mapping and facets would significantly improve the organization of code only entity definitions.  However, it should be optional, as dynamic (runtime) entity generation can only be attained through an  attribute-free approach as outlined above.  The ability to eventually define a hybrid of the two would eventually be effective as well.  This is significant considering the builder's CreateDatabaseScript feature. I emplore EF to consider implementing migration generation support for these scripts in subsequent releases, thus allowing deltas to be specified I'm dbscripts as code only entitiy classes and relationships evolve over time. The CTP looks promising.

  • Anonymous
    August 04, 2009
    Steven, nothing stops you from writing your code that way if you prefer. The same property builder type works as Alex demonstrated and as your example shows.

  • Anonymous
    August 04, 2009
    @Stefan. The design makes it relatively easy to layer above it. For example something that looks at attributes to then configures your mappings accordingly. Alex

  • Anonymous
    August 04, 2009
    Looks nice, but I have a question / concern? Are you going to provide "convention over configuration" style mappings. For example, assume I have a class User class User {  public string Name { get; set; }  public string Surname { get; set; }  public int? Age { get; set; } } Will be there any way to automap this class to table [User] with columns: NVARCHAR Name NOT NULL, NVARCHAR Surname NOT NULL INT Age NULL Thanks in advance!

  • Anonymous
    August 04, 2009
    @Sergejus, This is essentially the default. All you need is a property on your Context class (i.e. public ObjectSet<User> Users {...} (see the previous post on CodeOnly for more on this) Then what you ask for happens by default (except the table will be called 'dbo.Users') If you really want it to be called 'User' you do this: builder.Tables["User"] = new EntityConfiguration<User>(); and you are done. -Alex

  • Anonymous
    August 04, 2009
    @Sergejus That is effectively what the first CTP did although the NVARCHAR's would be default to being nullable given that String is nullable. [)amien

  • Anonymous
    August 04, 2009
    Thinking Entity Framework would really benefit from System.ComponentModel.DataAnnotions when specifying "Property Facets" (MaxLength etc). At least when you use Code Only/POCO.

  • Anonymous
    August 05, 2009
    I like where most things are going. However, I just hope everything is supported no matter which database you choose (allowing developers more flexibility). SQL CE is the logical db choice for most of my group's apps, however, Identity Columns are not supported by EF for CE, even though both EF and CE support them on their own.

  • Anonymous
    August 05, 2009
    Yes I think, all looks very promising, but as always if you want to use EF and SQL CE it looks all very different and you cannot use it, because of a small part missing. Till now, there is no info about SQL CE 4, I think we can just dream of using EF and SQL CE.

  • Anonymous
    August 06, 2009
    @MikeH & @Scott We understand that other databases like SQLCE are super important, and we are trying to figure out how we can support CreateDatabase etc across all providers. Still until we've worked through these issues early versions of CodeOnly will be limited to SQLServer. Alex

  • Anonymous
    August 06, 2009
    @Juliën, I agree that supporting DataAnnotations makes a lot of sense. The good news is it should be possible to layer in support for them later. -Alex

  • Anonymous
    August 11, 2009
    I would love it if the default of two entities having navigation properties to each other would be to assume those were inverses, as that will likely be the case most of the time!

  • Anonymous
    August 12, 2009
    when is the entity map stuff available?

  • Anonymous
    August 14, 2009
    @Alex & Julien: Even better it would be to specify the DataAnnotations in the designer with a good annotations Wizard. And i'm still missing the "between propery" and "between entity" business rules (i.e. enddate is null or >= startdate). Still a lot can be learned from existing validation frameworks :)

  • Anonymous
    August 26, 2009
    It's a good progression and I'm glad that things are moving along. Here are my opinions on it:

  1. I'm not completely sure how much i like the anonymous type mapping of all the properties. Sure it's no magic strings to match column names, but there's no advantage over strings either.
  2. Inheritance should be a first class citizen in mapping. Fluent NHibernate had this issue until recently also. You shouldn't have to map the same properties to the same names on derived classes (with the exception of TPC). This will lead to bugs and make mappings hard to maintain (especially when the types are anonymous). Another issue that arises is when you are doing all those unions and such, you are required to have all of your mappings in one assembly. Currently, i have a mapping assembly for each distinct section of code, and we build a session factory out of all the mappings that are added as a reference to a project. This makes it so that future additions to a project are easy to add dynamically as well as the mappings can be shared among multiple projects without having to support EVERYTHING for all projects. For instance, we have a mapping for HR, a mapping for Payroll, and a mapping for Project Management. Payroll has no need to map any of the entities in Project Management, but HR needs to use the entities for both Payroll and Project Management. Since mapping and building the session factories / contexts seems to be somewhat expensive, why do more than you have to? I think the best way to solve this is with a class dedicated to derived types (in the case of fluent nhibernate, SubclassMap<T>). This way you don't have to look up what members are mapped to another table in the base type unless they explicitly map that the type they are mapping has inherited fields that may already be mapped. It will make it harder to determine what is mapped, but I think that it is worth it, and I bet the fluent nhibernate can attest to it as well (as they re-engineered the system to support it).
  3. I really like the way you are doing foreign keys. I especially like the syntax where you can specify the foreign key column via p.Customer.ID approach. I like the syntax for association tables as well. Conclusion: Personally, I prefer the EntityMap<T> approach over the linq approach and will probably lean towards using that. I also quite like the fluent approach over a property approach as well. I'm quite happy with how this code only API is progressing and am eager to see how the team enhances this API heading towards release.
  • Anonymous
    September 30, 2009
    Is it possible to configure a context with a hierarchy of classes defined in several different assemblies? were eagerly awaiting the availability of this release!

  • Anonymous
    September 30, 2009
    @Nabil, Yeap it is possible to spread your classes around different assemblies Alex

  • Anonymous
    October 01, 2009
    The comment has been removed

  • Anonymous
    October 01, 2009
    I made some simple tests, and the result is that heritage through many assemblies unfortunately is not supported, this poses major problems. 1-How-to work with large model, it is incense to define an entire tree of class in the same assembly. 2 Applications extension by inheritance  strategy: it impossible to extend a class in new assembly. This is a vital need to design flexible system. I am interested to know what you plan for this.

  • Anonymous
    October 02, 2009
    @efdesign I made some simple tests, and the result is that working with context class to manage a set of class with an inheritance defined in different assemblies unfortunately is not supported!!!!

  • Anonymous
    October 02, 2009
    @Nabil Sedoud The problem is that the additional assemblies are not being discovered automatically. If you tell the ContextBuilder about types from the other assemblies, e.g. builder.Entity<TypeFromOtherAssembly>() those assemblies will also be used to discover the type hierarchy.

  • Anonymous
    October 09, 2009
    Thanks for the article. Is it possible to get Table name from the corresponding entity type? Thanks