Compartir a través de


Code Only – Further Enhancements

We’ve come a long way since the last post on Code-Only. So it’s high time for another update.

We’ve been working really hard on Code-Only revving the design, and spotting missing capabilities and responding to feedback both internal and external etc. 

The current plan still holds.  Code-Only will not be in .Net 4.0 with the possible exception of the DDL features described in the last post.  For that portion, the implementation and requirements are more clear to us, and because making the DDL stuff work requires changes to things already in the .NET framework, and getting provider writers lined up, we are still working hard to get the DDL changes into .NET 4.0.  The rest of Code-Only will continue to evolve, and we will ship another preview or two, before getting it rolled into .NET as soon as we can after 4.0 ships.

API Refactoring

As hinted above, we’ve spent a lot of time validating code only against real world scenarios, and thinking about how the customer code hangs together etc. As a results we’ve done some API refactoring.

Mappings are now part of Configurations

In the last version Mappings were derived from Configurations, which had some strange side-effects on the API.

We’ve re-arranged things now so that Configurations hold mappings, which are terminated by assignment to a table.

builder.Entity<Customer>()
.HasKey(c => c.ID)
.MapSingleType(c => new {
cid = c.ID,
nme = c.Name
}
)
.ToTable(“dbo.custs”);

This maps the Customer entity to the ‘dbo.Custs’ table, the ID property to the cid column, the Name property to the nme column, and registers the ID property as the EntityKey / Primary Key.

As you can see we’ve also added a helper method called Entity<TEntity>() so that you can fluently create configurations and mappings. You might also have noticed that we have removed RegisterKey and replaced it with HasKey which we feel is more inline with our goal of having an intentional API.

TPH mapping syntax no longer violates DRY

The previous syntax for specifying a TPH mapping forced you to repeat mappings for columns that could easily have been inherited.

This was unfortunate because we normally encourage TPH because generally it has the best performance characteristics.

This is where MapHierarchy() and Case() come in.

Imagine if we want to write these classes using TPH:

public class Person{
public virtual int ID {get;set;}
public virtual string Firstname {get;set;}
public virtual string Surname {get;set;}
}
public class Employee: Person
{
public virtual Employee Manager {get;set;}
public virtual List<Employee> Reports {get;set;}
}

You would now do it like this:

builder
.Entity<Person>()
.HasKey(p => p.ID)
.MapHierarchy()
.Case<Person>(
p => new {
p.ID,
p.Firstname,
p.Surname,
thisIsADiscriminator = “P”
}
)
.Case<Employee>(
e => new {
manager = e.Manager.Id
thisIsADiscriminator = “E”
}
)
.ToTable(“dbo.People”);

By using Case<> mappings are re-used for derived type, so there is no need to repeat column mappings for ID, Firstname and Surname.

Which means the Employee Case statement is only responsible for mapping properties and references declared by the Employee class, and for specifying a new discriminator value.

The discriminator column and values are there so the Entity Framework can distinguish between rows in the dbo.People table that represent Person objects and those that represent Employee objects.

MapSingleType and MapHierarchy

Because Mappings are now part of the configuration, there is no need to create them independently and assign them.

You now simply MapXXX() methods on the EntityConfiguration.

The available mapping methods are:

  • MapSingleType(λ)
  • MapHierarchy(λ)
  • MapHierarchy().Case<TEntity>(λ).Case<TDerived>(λ).

If you know the Entity Frameworks MSL files these correspond to:

  • Type
  • OfType
  • OfType with an addition requirement for a type discriminator

These mappings can be combined in many interesting ways, but in general this table shows the recommended way to do most common mappings:

Scenario Code Notes
No Inheritance builder.Entity<A>()    .MapSingleType(λ)    .ToTable(“dbo.A”); Columns not mapped are not part of the Entity.
TPH builder.Entity<A>()    .MapHierarchy()    .Case<A>(λ)    .Case<B>(λ)    .Case<C>(λ)    .ToTable(“dbo.ABC”); Generally each Case expression only maps properties declared by the current type and the discriminator for that type. But it is possible to override the mapping for inherited properties if required.
TPT builder.Entity<A>()    .MapHierarchy(λ)    .ToTable(“dbo.A”); builder.Entity<B>()    .MapHierarchy(λ)    .ToTable(“dbo.B”); builder.Entity<C>()    .MapHierarchy(λ)    .ToTable(“dbo.C”); Each MapHierarchy expression only maps properties declared by the current type, and properties that make up the entity key.
TPC builder.Entity<A>()    .MapSingleType(λ)    .ToTable(“dbo.A”); builder.Entity<B>()    .MapSingleType(λ)    .ToTable(“dbo.B”); builder.Entity<C>()    .MapSingleType(λ)    .ToTable(“dbo.C”); Each MapSingleType expression maps all properties, both those declared by the current type and those inherited.

Foreign Keys

In Beta2 of .NET 4.0 we added FK Association Support, so Code-Only needs a way to link a FK property and an Navigation Property together.

Given this class:

public class Product{
public virtual int ID {get;set;}
public virtual string Name {get;set;}
public virtual Category Category {get;set;}
public virtual int CategoryID {get;set;}
}

You need to be able to tell code-only that Category.ID and CategoryID should have the same value.

You do it like this:

builder.Entity<Product>()
.Relationship(p => p.Category)
.HasConstraint((p,c) => p.CategoryID == c.ID);

This says that c.ID (i.e. p.Category.ID) must equal p.CategoryID, which tells code-only that p.CategoryID is an FK property and p.Category is a navigation property backed by this FK property.

HasConstraint(λ) can also be used in conjunction with Relationship(λ).FromProperty(λ) like this:

builder.Entity<Product>()
.Relationship(p => p.Category)
.FromProperty(c => c.Products)
.HasConstraint((p,c) => p.CategoryID == c.ID);

Which tells code only that Product.Category and Category.Products are inverses and that Product.CategoryID and Product.Category.ID must be the same, which implies Product.CategoryID is an FK property.

Missing Navigation Properties

Sometimes you don’t have a navigation property or FK property in the Entity which would naturally hold the FK in the database. For example imagine this scenario:

public class Product{
public virtual int ID {get;set;}
public virtual string Name {get;set;}
}

public class Category{
public virtual int ID {get;set;}
public virtual string Name {get;set;}
public virtual List<Product> Products {get;set;}
}

Here there is ‘probably’ a one to many relationship between Categories and Products, and that is probably best modeled using an FK in the products table.

If you start mapping the Product entity you need a way to map the FK column, but there is no reference (i.e. Product.Category) and no FK property (i.e. Product.CategoryID) to map.

So we added the ability to create a fake navigation property to help out:

builder.Entity<Product>().MapSingleType(
p => new {
pid = p.ID,
nme = p.Name
cid = EntityMap.Related<Category>(c => c.Products).ID
}
).ToTable(“dbo.Products”);

Here the interesting part is EntityMap.Related, it creates a fake navigation property, just so you can use it in the mapping.

The method signature looks like this:

public static TEntity Related<TEntity>(
Expression<Func<TEntity, object>
);

Notice that this function simply returns TEntity, so in the above mapping fragment, we are mapping the ID property of the Category class to the cid column, in the ‘dbo.Products’ table.

Complex Types

Code-Only’s default behavior is to ignore properties that are not recognized as either a primitive type or an EntityType. So to support Complex Types we need a mechanism to register one, like this:

var addressConf = builder.ComplexType<Address>();

This returns a ComplexTypeConfiguration through which you can configure the properties of the ComplexType in the same way you configure the properties of an Entity:

addressConf.Property(a => a.Street).HasMaxLength(100);
addressConf.Property(a => a.Zip).HasMaxLength(10);

Now if an entity is mapped by convention and it has a ComplexType property we will automatically introduce columns for each property of the ComplexType.

If you want to explicitly map the Entity mapping the ComplexType property is pretty simple too:

builder.Entity<Person>().MapSingleType(
p => new {
p.ID,
fn = p.Firstname,
sn = p.Surname,
street = p.Address.Street,
zip = p.Address.Zip
}
);

Registering Entity Sets

Some of our early CodeOnly adopters wanted to use Code Only without having an specialized ObjectContext, and in theory with is possible:

builder = contextBuilder.Create<ObjectContext>();
builder.Entity<Person>().HasKey(p => p.ID);

But what we found is you have no control over the name of the set generated. In this case we will generate an EntitySet called PersonSet, but what if I the name should be People?

To address this issues we have added this method:

builder.RegisterSet<Person>(“People”);

Not only is this useful for specifying names,  it also allows you to intentional specify what EntitySets you need. The alternative would be to encode your intent into calls to Entity<TEntity> and Relationship<TRelated>(..).

Association Mapping

We also need to add support for mapping associations that are not co-located with the Entity. For example take the relationship between Jobs and Candidates. If a candidate can apply for multiple jobs this is a classic example of a many to many relationship.

If you want to take control of how this relationship is mapped you need a way to specify the mapping for the relationship, like this:

builder
.Entity<Job>()
.Relationship(job => job.Applicants)
.FromProperty(candidate => candidate.Applications)
.Map(“dbo.JobApplications”,
(job,candidate) => new {
applicantId = candidate.Id,
jobId = job.Id
}
);

This does a number of things, first it indicates that Job.Candidates and Candidate.Applications are opposite ends of the same relationship.

Then the Map() call indicates that the relationship should be stored in the “dbo.JobApplications” join table, with a compound PK made up of the applicantId and jobId columns.

The applicantId holds the candidate.Id, and the jobId holds the job.Id, and in both cases Code Only will emit an FK constraint to ensure referential integrity. 

Interesting Column Names

The existing mapping syntax that uses anonymous types for the table and column definitions, limits you to column names that are valid CLR identifiers.

So for example if you need to map to a column with a space in the name you can’t.

To rectify this we added an alternate mapping syntax:

builder.Entity<Product>().MapSingleType(
p => EntityMap.Row(
EntityMap.Column(p.ID, “p i d”),
EntityMap.Column(p.Name),
EntityMap.Column(p.CategoryId, “c i d”)
)
).ToTable(“dbo.Products”);

This snippet of code, maps the Product entity to the “dbo.Products” table and the ID property to a ‘p i d’ column, the Name property to a Name column and the CategoryId property to the ‘c i d’ column.

This alternate API has another advantage too: it makes it easier to create mapping expressions programmatically, primarily because you no longer need to create an anonymous type when building the expression. Which is really useful if you want to configure code-only at runtime.

Extracting the EDMX

We’ve also added the ability to get the EDMX that Code Only is producing internally, either via an XmlWriter or as an XDocument:

DbConnection connection = // some code to get a connection.
XDocument document = builder.GetEdmx(connection);

// or using XmlWriter
var swriter = new StringWriter();
var xwriter = new XmlTextWriter(swriter);
builder.WriteEdmx(connection, xwriter);

This is pretty handy if you want to use Code Only to build your model and mappings but then pull them out so you work with the XML and our designer tooling.

Setting the StoreType

When producing the Storage Model Code Only asks the current database provider to return an appropriate store type for the CLR type and facets specified.

But some CLR types and facet combinations can map to multiple possible database types. For example fixed length byte[] can map to both binary and timestamp. So in this situation SqlClient returns binary by default.

But you might need a timestamp:

builder
.Entity<Order>()
.Property(o => o.Version)
.HasStoreType(“timestamp”)
.IsComputed();

This tells Code Only that the Version property of Order (which is a byte[]) is computed in the database, and is actually a ‘timestamp’ column, which is computed in the database after every insert / update.

DDL Provider Model

In our last post we covered our plans around the DDL provider model, so check that out.

Summary

As you can see Code Only is now looking much more complete.

But it isn’t completely finished yet, we are still working on the rough edges, so as always we are very interested to get your feedback.

In particular are there things you think we should add / rename / simplify?

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
    October 12, 2009
    Very early days. Even the code here doesn't comply with  basic c# naming conventions - ID is not an acronym. Source analysis will fail.

  • Anonymous
    October 12, 2009
    @RichB, Clearly there are different schools of thought on ID verses Id. But putting that aside for a moment, because it isn't actually part of code-only, what makes you say it is 'Very early days'? Alex

  • Anonymous
    October 12, 2009
    The only problem I have with this is the same problem I have with fluent NHibernate: It pushes people to create anaemic domain objects by forcing them to expose the database fields as properties. Then, because somebody has never seen a well-mapped domain, they say "Hey! I can code-gen this!" and you're left with what are basically typed datasets.

  • Anonymous
    October 12, 2009
    The comment has been removed

  • Anonymous
    October 12, 2009
    The comment has been removed

  • Anonymous
    October 12, 2009
    The comment has been removed

  • Anonymous
    October 12, 2009
    Will there be a story around creating conventions based on Code-Only?  For example, will I be able to set up a convention so that all classes in a given namespace get mapped 1:1 to a table of the same name, and use a property Id as a key, and have foreign keys mapped for child collections?  Given what you have shown, I could see this possibly working with a little bit of reflection, but I was wondering if this would be baked in somehow.

  • Anonymous
    October 12, 2009
    I have an address table that is used in several relationships to different entities, for example for a customer or a business.  The address table is modeled such that it holds the fk to the related entity along with text field that indicates which type of entity the fk goes to.  So an address that goes to customer with pk 3 would have the fk column with the value 3 and the "discriminator" column with a value of 'cust'.  An address that goes to a business with pk of 3 would have an fk of 3 with discriminator of 'bus'. Can EF code-only support this scenario?   -andy

  • Anonymous
    October 13, 2009
    Is there any advantage to mapping entities using the anonymous type versus EntityMap.Column? I would probably use the string based approach over the anonymous type method as it's a bit closer to what other frameworks use. Also, why do you need to do EntityMap.Row(EntityMap.Column))? I'm assuming because you need to return an object is why the method call, but why not something like EntityMap.Row().Column("").Column(""), or even EntityMap.Column("").Column(""), etc. Nested parenthesis in an anonymous delegate are always meh to me. Not a huge fan of them.

  • Anonymous
    October 13, 2009
    @Darren: You can't chaning the Column calls as they need to expose something that lets you specify constraints, nullability etc. and while theoretically we could add a Column interface to that it would get very confusing very quickly once you have those sorts of calls nested. e.g. EntityMap.Column("Name").IsRequired().Column("Address").HasPrecision(5).Column("A")... [)amien

  • Anonymous
    October 13, 2009
    The comment has been removed

  • Anonymous
    October 13, 2009
    @Damien good point. I would tend to agree with what Jeremy has proposed to avoid the nested parentheses problem. That's the only major beef i have with the current proposal.

  • Anonymous
    October 13, 2009
    @Darren & Jeremy There is another subtle benefit of using the lambda syntax we are using. Namely that it is possible to create classes that represent the table and map directly to an instance of it: MapSingleType(p => new PersonStructure {Id = p.Id, FN = p.Firstname, SN = p.Surname}) .ToTable(...) Now I'm not sure if this is that useful, but you never know. Alex

  • Anonymous
    October 13, 2009
    @Andy, The EF doesn't support that scenario directly but you could do something like this using views in the SSDL etc. But that falls outside the scope of things you can do with Code-Only directly Alex

  • Anonymous
    October 13, 2009
    @Daniel, The conventions used by Code-Only are hardcoded at the moment, but we are investigating what it would take to add some sort of pluggable conventions model. What sort of model do you think makes sense? Alex

  • Anonymous
    October 13, 2009
    Jeremy, Not 100% sure what you are driving at with your suggestions. What would object scoping look like? and what sort of nested closure construct are you thinking? Alex

  • Anonymous
    November 01, 2009
    Where can i find the ContextBuilder class?  Is it not included in VS2010 Beta2?

  • Anonymous
    November 02, 2009
    @Anton, ContextBuilder and the rest of code-only continues to be part of the feature CTP.  It is NOT part of the core product shipping in VS2010, so it's not part of beta 2.  We are working on an update to the feature CTP which will work on part of beta 2, and it should be available very soon.

  • Danny
  • Anonymous
    November 06, 2009
    Are the Data Services supported when Code Only is used? I tried to create a small example but I'm getting 'The 'metadata id' keyword is not supported.

  • Anonymous
    November 09, 2009
    @Tolo, The metadata id problem is a bug in Entity Framework (not code-only) that we fixed post Beta2. The only work-around at the moment is use GetEdmx() to extract the EDMX and split it onto disk and construct the ObjectContext using the more familar metadata=file://C:model.csdl;... syntax instead. The problem is that Astoria caches it's version of the metadata keyed on the metadata portion of the connection string, and then tries to parse that again using EntityConnectionStringBuilder, which fail if there is a metadata id, so if you load the metadata from disk, the problem goes away. I'll probably put something on my blog on this soon. Alex

  • Anonymous
    November 09, 2009
    I tried to do something similar but I'm absolutely new to Entity Framework and I had some problems about how to use the edmx. I will try again using your hints. Thanks for the help Alex.

  • Anonymous
    November 09, 2009
    I am making a new application and using C# 4.0 and EF4 CodeOnly for the same. I am loving CodeOnly functionality. One basic thing I cannot find is how can I exclude a public property to be mapped to the database. Is the only way to do this is to map all properties (except the one I don't want to persist) in the context builder?

  • Anonymous
    November 30, 2009
    How do I model a many to many relationship table using code only? eg. A table Student, Class, and StudentClass which has a foreign key to both. Classic many-to-many relationship model. In EDMX it would be something like:          &lt;Key&gt;            &lt;PropertyRef Name="StudentId" /&gt;            &lt;PropertyRef Name="ClassId" /&gt;          &lt;/Key&gt; Is there anything in EntityConfiguration? If not, what are your plans? I'd like to do something like Property(sc=>sc.StudentId).CompositeKey(); Property(sc=>sc.ClassId).CompositeKey(); Thanks! Shawn

  • Anonymous
    November 30, 2009
    Should have been more specific on that post... Taking your JobApplications example, how can I get a POCO that represents the JobApplications relationship table? In a real world scenario it might have the two foreign keys as well as other data/columns (eg. ApplicationTime)

  • Anonymous
    November 30, 2009
    @Shawn, Code-only doesn't change the way EF works with many-many relationships.  In the EF, if you have a link/relationship table that has only foreign keys to the related entities, then the EF can create a many-many relationship and with code-only this can be done by convention or by taking more control over things as described above. If you have additional data columns other than the foreign keys in your link table, then the EF will require you to create an entity for that table which has two 1-to-many relationships rather than just having the "outside" entities with a many-to-many relationship.

  • Danny
  • Anonymous
    December 16, 2009
    MYSQL error The given provider is not supported. Only System.Data.SqlClient is currently supported.

  • Anonymous
    December 29, 2009
    Is there a way to do Entity Splitting using code only using a foreign key?  I need my object populated from two tables, the first table has a column that is a foreign key that points to the second table.  You can do that using EDMX but I can't figure out how to do it with code only.   I have a more specific example here: http://stackoverflow.com/questions/1955358/entity-splitting-with-code-only

  • Anonymous
    January 09, 2010
    Hello, I have a domain object that should get some of its properties mapped to an ordinary table, and another property to a column of a view. This view is not updateable because of the CTE it contains. The key used to join table and view is a Guid which is created by the client, not the sql server. This is my domain class: public class MyDomainClass { public MyDomainClass() { Id = Guid.NewGuid(); }    public Guid Id {get; set;}    public string Name {get; set;}    public string Result {get; set;} // in view } I am using the current code for the mapping: HasKey<Guid>(x => x.Id); Property(f => f.Result).IsComputed(); MapSingleType(f => new { id = f.Id, Name = f.Name, }).ToTable("MyTable"); MapSingleType(f => new { id = f.Id, // for the join result = f.Result, // the result of a recursive CTE (for blazing performance) }).ToTable("MyView"); On Insert I am getting this exception: System.Data.UpdateException: An error occurred while updating the entries. See the inner exception for details. ---> System.Data.SqlClient.SqlException: Update or insert of view or function 'dbo.MyView' failed because it contains a derived or constant field. First SaveChanges() does an insert on MyTable for Id and Name. But then it tries to insert the Id into the view, which makes sense since it doesn't know its a view that already contains the Id. At least, it doesn't try to insert the Result property, because it was flagged with IsComputed(). I can't figure out how I can specify that the Id should not be saved to the view while it should still be saved to the table. I would like to suggest one of the following extensions: MapSingleType(...).ToView("MyView"); MapSingleType(...).ToTable("MyView").AsReadOnly(); MapSingleType(...).ToTable("MyView") .AsReadOnly(x => x.Id) .AsReadOnly(x => x.Result);  // Result already excluded by .IsComputed() These may also be applicable for the MapHierarchy() method. Specifying Stored Procedureds for CRUD could also solve the problem. Is there anything coming in the next preview release? Regards, Michael

  • Anonymous
    February 24, 2010
    Hello, Is it possible to map stored procedure in code only? Now i have only one way to use sp's - importing functions in the EDMX.

  • Anonymous
    February 25, 2010
    @kirill: This isn't possible right now but we are going to consider it for a future update. Thanks for your feedback! [)amien

  • Anonymous
    February 26, 2010
    What about the problem mentioned by Alun Harford? IMHO: This is very important guys for designing better domain models (at least for modeling Core Domain) - a paradigm shift from Strongly Typed Data Classes to Behavior only Domain Models.

  • Anonymous
    February 28, 2010
    The comment has been removed

  • Anonymous
    February 28, 2010
    @Mikael, There are a variety of cases where navigation properties end up being more than just a convenience.  In the case of RIA services, for instance, I'm pretty sure that the nav props are required at least in one of the two directions in order to make sure that the serialization knows how to hook together your related entities.  So I think you should consider building things to have the nav prop.

  • Danny
  • Anonymous
    February 28, 2010
    Thanks Danny, Its not a problem this time to do as you suggest, other than i need to add additional properties to my objects which i think is acceptable. But might be other scenarios where its not an option. So i figured there might be something similar to "Missing Navigation Properties" in RIA Services, since i already told the framework how to navigate. Anyway love this code-only stuff, great work.

  • Anonymous
    March 03, 2010
    Hi Alex, Great great stuff! How would one specify a different store table name as can be done with ToTable("dbo.TableName") on any of the MapXXX extensions WITHOUT having to map the whole entity again (DRY). I can do it like this builder.Entity<Person>()    .MapSingleType(        p => new            {               o.ID,               ... // Repeat all properties             })        .ToTable("dbo.MyPerson"); I would like to see builder.Entity<Person>()    .ToTable(dbo.MyPerson"); Have I missed something? Clint

  • Anonymous
    April 01, 2010
    this is heavy stuff but its a good practice for me thank you regards jan wensink.

  • Anonymous
    April 11, 2010
    Will Entity Framework supports horizontal partitioning over distributed databases in future. will it compete Hibernate in features . I expect Microsoft entity framework to be more scalable than any other matured OR/M tool

  • Anonymous
    April 28, 2010
    I need an entity with dynamic properties for user defined attributes. I use collection properties to resolve this. How do I map the data columns with a collection property to the entity? Something like this:            MapHierarchy(               p => EntityMap.Row(                   EntityMap.Column(p.ID, "id"),                   EntityMap.Column(p.UDA_Columns["Test"], "Test")               )            ).ToTable("Table"); Regards, Antonio

  • Anonymous
    April 28, 2010
    Unfortunately the EF doesn't support this kind of mapping today (with codefirst or other ways of specifying the mapping).  It only supports mapping of properties to scalar types.  Adding support for collection properties of this kind is something we intend to do in a future release.

  • Danny
  • Anonymous
    June 07, 2010
    The comment has been removed
  • Anonymous
    June 07, 2010
    The comment has been removed
  • Anonymous
    June 24, 2010
    Hello Alex, I have Employee Table. My requirement is to self join table to itself. Employee Table have below fields- EmployeeID Name ManagerID I want to acheive below two things through EF without making server trip.
  1. I want to get subordinates of all employees.
  2. I want to get manage name & ID of all employees. I tried but could not acheive above functionalities through EF. Is it possible to acheive desired functionalities in EF through self join approach? Please guide to resolve the required concerns. Regards, Vijay Sitlani
  • Anonymous
    September 09, 2010
    I  have problem with hieracrchy. I want to define a base entity and then drive all of my POCO from base. But I always get error Invalid descrimintor column. I do not have any descriminator column as by base entity is just abstract one. How can I resolve this issue? I will appereciate any help on this       [Serializable]       public class FSBaseEntity        {            public virtual Guid Id { get; set; }            public virtual Int32 HdVersionNo { get; set;}            public virtual Guid HdEditStamp { get; set;}        }       [Serializable]       public class PaymentOrder : FSBaseEntity       {           public virtual decimal OrderNo { get; set; }           public virtual byte OrderType { get; set; }           public virtual decimal SenderAccountNo { get; set; }       }