Tag Archives: Entity Framework
Isg.EntityFramework 0.9.0 Released (Bug Fix)

Release Notes

  • Fixed a bug in TypeInterceptor in which IsTargetEntity() was not being called before passing the handling down the inheritance chain.
Isg.EntityFramework 0.8.0 Released

InterceptionContext is now passed to TypeInterceptor methods and ChangeInterceptor methods. This may result in breaking changes depending on if and how you have inherited those classes, but I’ve done my best to preserve existing behavior. I marked the obsolete methods as such.

The purpose of this change is enable the scenario where you want to write a log-record back to the database when a record is saved or deleted.

Update:

0.8.0 did not contain the updated assemblies.

0.8.1 does.

What happened?

My build server is configured so that it only creates and publishes packages from the last pinned build. I forgot to pin the build that has the changes. I’ve pinned the build and republished 0.8.1. I’ve created a workitem for myself to separate package creation from package publication so that I can inspect the package before it’s sent to Nuget.

Isg.EntityFramework 0.7.0 Released–Bug Fix

Issues Fixed

  1. ChangeInterceptor.OnAfter not working correctly.

The cause of the bug is that EntityFramework resets the EntityState to UnChanged after writing the object to the database. This means that I cannot tell which operation was performed on the entity unless I memoize the state before the operation. The effect of this change is that I can no longer get the EntityState of the item before the operation from the DbEntityEntry.

Breaking Changes

If you are inheriting from TypeInterceptor or ChangeInterceptor the signature of OnBefore and OnAfter has changed.

The new signatures are

 protected override void OnBefore(DbEntityEntry item, EntityState state)
 protected override void OnAfter(DbEntityEntry item, EntityState state)

The Value of Features of ORM’s

At Redacted Associates, we’ve been having a discussion about whether we should use the Generic Repository pattern on top of NHibernate. We have a simple IRepository interface as follows:

For my part, I like don’t like to spend a lot of time worrying about the way my ORM interacts with the database. I prefer to spend my design-energy around how to architect the application such that interacting with the database is a minor implementation detail, almost an afterthought even.

At least one of my co-workers disagrees, and has given a really good argument for a specific case when using direct features in NHibernate saved some work. This discussion has spurred me to ask myself “what are the most important features of an ORM?” and “at what level of abstraction should we work with an ORM?” There’s no way to answer these questions without identifying your purpose in using the ORM to begin with. With that in mind, I decided to categorize the features we all look for in an ORM and compare them to our Generic Repository implementation.

ORM features basically fall into one of 3 categories:

  1. Queryability

    • Linq Provider

      In .NET, Linq remains the most discoverable way to query an ORM. NHibernate has the QueryOver api, but I find it to be hopelessly unreadable anything but the simplest query.

    • Specification pattern

      The easiest specification pattern in .NET relies on Linq. It’s a very nice way to allow api clients to construct their own queries without concerning themselves with database schema details. In an SOA architecture, it provides a flat contract to support complex query results. It minimizes the number of service methods because you don’t have to write GetCustomersByNameAndCityAndProduct.

    • Fully mapped relationships between objects.

      I depend on having a fully expressed query model to use for linq queries. Getting the right data to work with often involves a number of database relationships and it’s impossible to predict when some new object or property will be needed to satisfy a query. It’s easiest to ensure that the model fully expresses the database and that all fields and relationships are present in the model. The model should fully and accurately express the database schema.

    • Id navigation properties.

      Id navigation properties as a companion to the object relationship properties are really handy. They can reduce query-syntax clutter quite a bit. Employee.ManagerId is cleaner than Employee.Manager.Id. Some ORM’s will pull back the whole Manager to get the Id. I hate that.

    • Full support for all relationship types (one-to-one, one-to-many, many-to-many).

      These relationships are standard in relational db’s. Any Object-Relational Mapper should support them.

    • Lazy Loading
  2. Behavior

    • Cascade mappings.

      This is not personally a value to me, but I recognize that in some cases it’s useful.

    • Trigger-behavior.

      This sort of behavior is useful when you want the ORM to handle things like audit fields, soft deletes, or log table entries.

    • Sql-Efficiencies.

      Sometimes pulling back large datasets and mapping them to in-memory objects can be very expensive. If performance is a concern, it’s nice to be able to have the ORM optimize the operation. NHibernate’s “Merge” operation is a good example of this.

  3. Testability

    • In-memory testability
    • Mockable/Stubbable

I composed the following table listing the features we are seeking from an ORM and how each tool fares against our needs.

NHibernate
Entity Framework
Generic Repository
Micro ORMs
Linq Provider
Not Fully Implemented Fully Implemented Depends on ORM No
Specification Pattern
Easy to implement against partially implemented Linq provider. Hard otherwise. Easy to implement. Depends on ORM No
Can Fully Map Relationships
Yes Yes Depends on ORM No
Id Navigation Properties
Not without extreme (and not very useful) hacks Yes Depends on ORM Yes
Full support for relationship types
One-to-one doesn’t work well. Results in N+1 queries on one side of the relationship * Haven’t tested this. Depends on ORM Sort of
Lazy Loading
Yes Yes Depends on ORM No
Cascade Mappings
Yes Yes Depends on ORM No
Trigger Behavior
Yes Yes Depends on ORM No
Sql Efficiences
Yes Yes Depends on ORM No
In-memory testability
Yes, provided you use SqlLite. Yes, provided you use SqlCompact edition—or you can interface your DbContext. Yes No
Mockable-Stubbable
Mostly. Some of the methods you use on ISession are in fact extension methods. .Query is an extension method which is problematic since that’s an obvious one I’d like to stub. Mostly. Methods such as .Include() are extension methods with no in-memory counterpart. If I really need to use .Include() I’ll write an integration test instead. Yes No
Notes
  • ISession.Query is an extension method. Not testable.
  • ISession.QueryOver is a difficult api for complex queries, especially those involving groups.
  • Automappings make map-by-configuration easier.
  • No (current) support for convention-based configuration.
  • No Merge operation.
  • Generic repository does not give you direct access to methods like ISession.Merge.
  • You have two ways of dealing with this:
    1. Use ISession (or DbContext) directly.
    2. You can inherit or adapt the IRepository interface to provide the other methods.
Fantastic for quick and dirty CRUD on single tables. Not so great otherwise.

 

Takeaways

My advice is to use Generic Repository except when you need to get close to the metal of your ORM for some fine-grained control over your data access.

Isg.EntityFramework.ObservableProvider

The Problem

I’ve been struggling for awhile to figure out how to get Entity Framework to set and unset application roles in Sql Server when opening and before closing a connection. The problem is that ConnectionState does not provide a Closing state that fires before a connection is closed.

It was suggested to me to turn of connection pooling. Errrr, no. We want connection pooling for our applications. I also don’t want to have to manually open and close the connection every time I create a DbContext. That’s just messy and irritating.

The next obvious thing to do would be to create a DbConnectionDecorator to wrap the existing database connection and expose the Closing event. This proved to be very difficult because Entity Framework does not expose when and how it opens connections.

Grrrrrr.

The Solution

What’s that you say? I can implement my own EntityFramework Provider? There’s a provider wrapper toolkit I can plug into to do this? Awesome!

Oh wait—that looks really, really, REALLY complicated? You mean I can’t just decorate a single object? I have to decorate a whole family of objects?

Hmmmm.

Alright, tell you what I’ll do. I’ll implement the provider wrapper using the toolkit as best I can—but then I’m going to strip away everything I don’t actually need. Besides, if I just make the various data related events observable, it’s nothing to Trace the output. And Cacheing can easily be added as a IDbContext Decorator anyway. I don’t really get why that should be done at the Provider level.

Configuring Your Application to use the Provider

To use the new provider, first install the package. At application startup, you’ll need to register the provider and tell it which other provider you are wrapping. The registration process will set the ObservableConnectionFactory as the default connection factory used by EF unless you pass the optional setAsDefault:false.

Consuming the Provider Events

ObservableConnection exposes several new events, including Opening, Opened, Failed, Closing, and Closed. However, to subscribe to those events directly requires that you cast the DbConnection exposed by EF to ObservableDbConnection, which strikes me as a little cumbersome (not to mention a violation of the LSP). My first use case demands that I handle the Opening and Closing events the same way for every DbConnection application-wide. To that end, ObservableDbConnection (and ObservableDbCommand) pushes its event messages onto a static class called Hub.

Guarantees

This code is brand-spanking new and it hasn’t had time to bake yet. I’m using it, but it’s entirely possible that there are unforeseen problems. Feel free to report issues to and/or contribute to the open-source project on BitBucket. Until then, know that it has been rigorously test and that it works on my machine.

works on my machine, starburst

Isg.EntityFramework 0.5.1 – Release Notes

I’ve added a LookUp property to the InterceptionContext to make it easier to see which rows were affected during the After() interception phase.

    public class InterceptionContext
    {
        public DbContextBase DbContext { get; internal set; }
        public ObjectContext ObjectContext { get; internal set; }
        public ObjectStateManager ObjectStateManager { get; internal set; }
        public DbChangeTracker ChangeTracker { get; internal set; }
        public IEnumerable<DbEntityEntry> Entries { get; internal set; }
        public ILookup<EntityState, DbEntityEntry> EntriesByState { get; internal set; }

... snipped for brevity

EntriesByState is populated prior to the call to Before(). Added and Modified entities will have their EntityState reverted to UnChanged after SaveChanges() is called. EntriesByState preserves the original state of the entities so that After() interceptors can make use of new Id’s and such.

Isg.EntityFramework 0.4.2 Released to NuGet

Isg.EntityFramework.Interceptors was renamed to Isg.EntityFramework. The interceptors are still present, but I’ve added some work around EntityFramework Configuration.

Changes:

  • InterceptorDbContext is obsolete. Please use Isg.EntityFramework.DbContextBase
  • DbContextBase adds support for a ModelConfigurationProvider
  • The default ModelConfigurationProvider scans the assembly that your DbContextBase subclass lives in and finds all instances of EntityTypeConfiguration<> and loads them.
  • I added some extension methods to make specifying a non-identity primary key column and computed column a little more straightforward. The EntityFramework method works fine, but isn’t very discoverable. Now it’s Property(<expression>).IsIdentity(false) and Property(<expression>).Computed().
  • Isg.EntityFramework is built against EntityFramework 4.3

Isg.EntityFramework is growing slowly but surely. I would love to hear your ideas about what features you would like to see.

Enjoy!

Using the EntityFramework SoftDelete Interceptor

In my previous post I introduced the Isg.EntityFramework.Interceptors I created. In this post, I will demonstrate the usage of another package that builds on the first: Isg.EntityFramework.Interceptors.SoftDelete

The entire SoftDelete package consists of two classes. The first is an interface:

   1:      public interface ISoftDelete
   2:      {
   3:          bool IsDeleted { get; set; }
   4:      }

The second is the SoftDeleteChangeInterceptor class.

If your domain class implements ISoftDelete, the SoftDeleteChangeInterceptor will catch the delete operation, set the IsDeleted field to true, and repurpose the operation to an Update.

Here is a unit test that demonstrates this behavior.

   1:          [Test]
   2:          public void Delete()
   3:          {
   4:              var name = Guid.NewGuid().ToString();
   5:   
   6:              using (var db = new CustomerDbContext())
   7:              {
   8:                  var customer = new Customer {IsDeleted = false, Name = name};
   9:                  db.Customers.Add(customer);
  10:                  db.SaveChanges();
  11:              }
  12:   
  13:              using (var db = new CustomerDbContext())
  14:              {
  15:                  var customer = db.Customers.SingleOrDefault(i => i.Name == name);
  16:                  db.Customers.Remove(customer);
  17:                  db.SaveChanges();
  18:              }
  19:   
  20:              using (var db = new CustomerDbContext())
  21:              {
  22:                  var customer = db.Customers.SingleOrDefault(i => i.Name == name);
  23:                  Assert.That(customer, Is.Not.Null);
  24:                  Assert.That(customer.IsDeleted, Is.True);
  25:              }
  26:          }

 

Happy Coding!

Implementing EF4 Change Interceptors

Interceptors are a great way to handle some repetitive and predictable data management tasks. NHibernate has good support for Interceptors both at the change and query levels. I wondered how hard it would be to write interceptors for the new EF4 CTP and was surprised at how easy it actually was… well, for the change interceptors anyway. It looks like query interceptors would require a complete reimplementation of the Linq provider—not something I feel like undertaking right now.

On to the Code!

This is the first interface we’ll use to create a class that can respond to changes in the EF4 data context.

  1. namespace Yodelay.Data.Entity
  2. {
  3.     /// <summary>
  4.     /// Interface to support taking some action in response
  5.     /// to some activity taking place on an ObjectStateEntry item.
  6.     /// </summary>
  7.     public interface IInterceptor
  8.     {
  9.         void Before(ObjectStateEntry item);
  10.         void After(ObjectStateEntry item);
  11.     }
  12. }

 

We’ll also use this interface to add some conditional execution support.

  1. namespace Yodelay.Data.Entity
  2. {
  3.     /// <summary>
  4.     /// Adds conditional execution to an IInterceptor.
  5.     /// </summary>
  6.     public interface IConditionalInterceptor : IInterceptor
  7.     {
  8.         bool IsTargetEntity(ObjectStateEntry item);        
  9.     }
  10. }

 

The first interceptor I want to write is one that manages four audit columns automatically. First I need an interface that provides the audit columns:

  1. public interface IAuditEntity
  2. {
  3.     DateTime InsertDateTime { get; set; }
  4.     DateTime UpdateDateTime { get; set; }
  5.     string InsertUser { get; set; }
  6.     string UpdateUser { get; set; }
  7. }

The EF4 DbContext class provides an override for SaveChanges() that I can use to start handling the events. I decided to subclass DbContext and add the interception capability to the new class. I snipped the constructors for brevity, but all of the constructors from the base class are bubbled.

  1. public class DataContext : DbContext
  2. {
  3.     private readonly List<IInterceptor> _interceptors = new List<IInterceptor>();
  4.     public List<IInterceptor> Interceptors
  5.     {
  6.         get { return this._interceptors; }
  7.     }
  8.  
  9.     private void InterceptBefore(ObjectStateEntry item)
  10.     {
  11.         this.Interceptors.ForEach(intercept => intercept.Before(item));
  12.     }
  13.  
  14.     private void InterceptAfter(ObjectStateEntry item)
  15.     {
  16.         this.Interceptors.ForEach(intercept => intercept.After(item));
  17.     }
  18.  
  19.     public override int SaveChanges()
  20.     {
  21.         const EntityState entitiesToTrack = EntityState.Added |
  22.                                             EntityState.Modified |
  23.                                             EntityState.Deleted;
  24.         var elementsToSave =
  25.             this.ObjectContext
  26.                 .ObjectStateManager
  27.                 .GetObjectStateEntries(entitiesToTrack)
  28.                 .ToList();
  29.  
  30.         elementsToSave.ForEach(InterceptBefore);
  31.  
  32.         var result = base.SaveChanges();
  33.         elementsToSave.ForEach(InterceptAfter);
  34.         return result;
  35.     }

 

I only want the AuditChangeInterceptor to fire if the object implements the IAuditEntity interface. I could have directly implemented IConditionalInterceptor, but I decided to extract the object-type criteria into a super-class.

  1. public abstract class TypeInterceptor : IConditionalInterceptor
  2. {
  3.     private readonly System.Type _targetType;
  4.     public Type TargetType { get { return _targetType; }}
  5.  
  6.     protected TypeInterceptor(System.Type targetType)
  7.     {
  8.         this._targetType = targetType;
  9.     }
  10.  
  11.     public virtual bool IsTargetEntity(ObjectStateEntry item)
  12.     {
  13.         return item.State != EntityState.Detached &&
  14.             this.TargetType.IsInstanceOfType(item.Entity);
  15.     }
  16.  
  17.     public void Before(ObjectStateEntry item)
  18.     {
  19.         if (this.IsTargetEntity(item))
  20.             this.OnBefore(item);
  21.     }
  22.  
  23.     protected abstract void OnBefore(ObjectStateEntry item);
  24.  
  25.     public void After(ObjectStateEntry item)
  26.     {
  27.         if (this.IsTargetEntity(item))
  28.             this.OnAfter(item);
  29.     }
  30.  
  31.     protected abstract void OnAfter(ObjectStateEntry item);
  32. }

 

I also decided that the super-class should provide obvious method-overrides for BeforeInsert, AfterInsert, BeforeUpdate, etc.. For that I created a generic class that sub-classes TypeInterceptor and provides friendlier methods to work with.

  1. public class ChangeInterceptor<T> : TypeInterceptor
  2. {
  3.     #region Overrides of Interceptor
  4.  
  5.     protected override void OnBefore(ObjectStateEntry item)
  6.     {
  7.         T tItem = (T) item.Entity;
  8.         switch(item.State)
  9.         {
  10.             case EntityState.Added:
  11.                 this.OnBeforeInsert(item.ObjectStateManager, tItem);
  12.                 break;
  13.             case EntityState.Deleted:
  14.                 this.OnBeforeDelete(item.ObjectStateManager, tItem);
  15.                 break;
  16.             case EntityState.Modified:
  17.                 this.OnBeforeUpdate(item.ObjectStateManager, tItem);
  18.                 break;
  19.         }
  20.     }
  21.  
  22.     protected override void  OnAfter(ObjectStateEntry item)
  23.     {
  24.         T tItem = (T)item.Entity;
  25.         switch (item.State)
  26.         {
  27.             case EntityState.Added:
  28.                 this.OnAfterInsert(item.ObjectStateManager, tItem);
  29.                 break;
  30.             case EntityState.Deleted:
  31.                 this.OnAfterDelete(item.ObjectStateManager, tItem);
  32.                 break;
  33.             case EntityState.Modified:
  34.                 this.OnAfterUpdate(item.ObjectStateManager, tItem);
  35.                 break;
  36.         }        
  37.     }
  38.  
  39.     #endregion
  40.  
  41.     public virtual void OnBeforeInsert(ObjectStateManager manager, T item)
  42.     {
  43.         return;
  44.     }
  45.  
  46.     public virtual void OnAfterInsert(ObjectStateManager manager, T item)
  47.     {
  48.         return;
  49.     }
  50.  
  51.     public virtual void OnBeforeUpdate(ObjectStateManager manager, T item)
  52.     {
  53.         return;
  54.     }
  55.  
  56.     public virtual void OnAfterUpdate(ObjectStateManager manager, T item)
  57.     {
  58.         return;
  59.     }
  60.  
  61.     public virtual void OnBeforeDelete(ObjectStateManager manager, T item)
  62.     {
  63.         return;
  64.     }
  65.  
  66.     public virtual void OnAfterDelete(ObjectStateManager manager, T item)
  67.     {
  68.         return;
  69.     }
  70.  
  71.     public ChangeInterceptor() : base(typeof(T))
  72.     {
  73.         
  74.     }
  75. }

 

Finally, I created subclassed ChangeInterceptor<IAuditEntity>.

  1. public class AuditChangeInterceptor : ChangeInterceptor<IAuditEntity>
  2. {
  3.     public override void OnBeforeInsert(ObjectStateManager manager, IAuditEntity item)
  4.     {
  5.         base.OnBeforeInsert(manager, item);
  6.  
  7.         item.InsertDateTime = DateTime.Now;
  8.         item.InsertUser = System.Threading.Thread.CurrentPrincipal.Identity.Name;
  9.         item.UpdateDateTime = DateTime.Now;
  10.         item.UpdateUser = System.Threading.Thread.CurrentPrincipal.Identity.Name;
  11.     }
  12.  
  13.     public override void OnBeforeUpdate(ObjectStateManager manager, IAuditEntity item)
  14.     {
  15.         base.OnBeforeUpdate(manager, item);
  16.         item.UpdateDateTime = DateTime.Now;
  17.         item.UpdateUser = System.Threading.Thread.CurrentPrincipal.Identity.Name;
  18.     }
  19. }

 

I plugged this into my app, and it worked on the first go.

Another common scenario I encounter is “soft-deletes.” A “soft-delete” is a virtual record deletion that does not actual remove the record from the database. Instead it sets an IsDeleted flag on the record, and the record is then excluded from other queries. The problem with soft-deletes is that developers and report writers always have to remember to add the “IsDeleted == false” criteria to every query in the system that touches the affected records. It would be great to replace the standard delete functionality with a soft-delete, and to modify the IQueryable to return only records for which “IsDeleted == false.” Unfortunately, I was unable to find a clean way to add query-interceptors to the data model to keep deleted records from being returned. However, I was able to get the basic soft-delete ChangeInterceptor to work. Here is that code.

 

  1. public interface ISoftDelete
  2. {
  3.     bool IsDeleted { get; set; }
  4. }

 

  1. public class SoftDeleteChangeInterceptor : ChangeInterceptor<ISoftDelete>
  2. {
  3.     public override void OnBeforeInsert(ObjectStateManager manager, ISoftDelete item)
  4.     {
  5.         base.OnBeforeInsert(manager, item);
  6.         item.IsDeleted = false;
  7.     }
  8.  
  9.     public override void OnBeforeDelete(ObjectStateManager manager, ISoftDelete item)
  10.     {
  11.         if (item.IsDeleted)
  12.             throw new InvalidOperationException("Item is already deleted.");
  13.         
  14.         base.OnBeforeDelete(manager, item);
  15.         item.IsDeleted = true;
  16.         manager.ChangeObjectState(item, EntityState.Modified);
  17.     }
  18. }

 

Here’s the complete diagram of the code:

image

EF4 has come a long way with respect to supporting extensibility. It still needs query-interceptors to be feature-parable with other ORM tools such as NHibernate, but I suspect that it is just a matter of time before the MS developers get around to adding that functionality. For now, you can use the interceptor model I’ve demo’ed here to add functionality to your data models. Perhaps you could use them to add logging, validation, or security checks to your models. What can you come up with?

Understanding the Fluent Configuration API for One-To-Many Relationships in “Code-First” EF4

Configuring ORM’s through fluent api calls is relatively new to me. For the last three years or so I’ve been using EF1 and Linq-To-Sql as my data modeling tools of choice. My first exposure to a code-first ORM tool came in June when I started working with Fluent NHibernate. As interesting as that has been, I hadn’t really be faced with the issue of proper configuration because I’ve had someone on our team that does it easily. This weekend I started working on a sample project using the EF4 CTP, and the biggest stumbling block has been modeling the relationships.

The context code is a SCRUM process management app I’m writing. Here’s the model:

Model

The relationship I tried to model was the one between Project and Story via the Backlog property. Since “Project” is the first entity I needed to write, my natural inclination was to model the relationship to “Story” first.

  1. public class DataContext : DbContext
  2. {
  3.     public DbSet<Project> Projects { get; set; }
  4.     public DbSet<Resource> Resources { get; set; }
  5.     public DbSet<Sprint> Sprints { get; set; }
  6.     public DbSet<Story> Stories { get; set; }
  7.     public DbSet<StoryStatus> StoryStatuses { get; set; }
  8.     public DbSet<Task> Tasks { get; set; }
  9.  
  10.     protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder builder)
  11.     {
  12.         base.OnModelCreating(builder);
  13.  
  14.         builder
  15.             .Entity<Project>()
  16.             .HasMany(e => e.BackLog);

I knew that “Story” would be slightly more complex because it has two properties that map back to “Project.” These are the “Project” property ,and the “ProjectId” property. Some of the EF4 samples I found refer to a “Relationship” extension method that I was unable to find in the API, so I was fairly confused. I finally figured out what I needed to do by reading this post from Scott Hanselman, though he doesn’t specifically highlight the question I was trying to answer.

This is the mapping code I created for “Story:”

  1. builder.Entity<Story>()
  2.     .HasRequired(s => s.Project)
  3.     .HasConstraint((story, project) => story.ProjectId == project.Id);

and this is the code I’m using to create a new Story for a project:

  1. [HttpPost]
  2. public ActionResult Create(Story model)
  3. {
  4.     using (var context = new DataContext())
  5.     {
  6.         var project = context.Projects.Single(p => p.Id == model.ProjectId);
  7.         project.BackLog.Add(model);
  8.         context.SaveChanges();
  9.         return RedirectToAction("Backlog", "Story", new {id = project.Id});
  10.     }
  11. }

 

When I tried to save the story, the data context threw various exceptions. I thought I could avoid the problem by rewriting the code so that I was just adding the story directly to the “Stories” table on the DataContext (which I think will ultimately be the right thing as it saves an unnecessary database call), but that would have been hacking around the problem and not really understanding what was wrong with what I was doing. It just took some staring at Scott Hanselman’s code sample for awhile to realize what was wrong with my approach. Before I explain it, let me show you the mapping code that works.

  1. protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder builder)
  2. {
  3.     base.OnModelCreating(builder);
  4.  
  5.     builder
  6.         .Entity<Project>()
  7.         .Property(p => p.Title)
  8.         .IsRequired();
  9.  
  10.     // api no longer has Relationship() extension method.
  11.     builder.Entity<Story>()
  12.         .HasRequired(s => s.Project)
  13.         .WithMany(p => p.BackLog)
  14.         .HasConstraint((story, project) => story.ProjectId == project.Id);
  15.  
  16. }

Notice what’s missing? I completely yanked the mapping of “Project->Stories” from the “Project” model’s perspective. Instead, I map the one-to-many relationship from the child-entity’s perspective, i.e., “Story.” Here’s how to read the mapping.

  1. // api no longer has Relationship() extension method.
  2. builder.Entity<Story>() // The entity story
  3.     .HasRequired(s => s.Project) // has a required property called "Project" of type "Project"
  4.     .WithMany(p => p.BackLog) // and "Project" has a reference back to "Story" through it's "Backlog" collection property
  5.     .HasConstraint((story, project) => story.ProjectId == project.Id); // and Story.ProjectId is the same as Story.Project.Id
  6. ;

The key here is understanding that one-to-many relationships must be modeled from the perspective of the child-entity.