-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow entity type instance to implement/inherit from type in model #13344
Comments
I experienced a challenge that might be addressed by this enhancement. However I view it from a slightly different perspective. Let's first talk about base classes rather than interfaces. When considering a base class, since EF already has a database implementation for inheritance, the question is really about how to execute queries from the base class. Specifically if there is a navigation relationship that specifies the base class as the dependent type, then how should we execute a query (such as Include) based on that navigation? To me it seems clear that we should find the table implementing the class hierarchy and load all entities derived from that navigation type, resulting in a list of heterogeneous type (but all derived from the base type). This would be consistent with normal object design concepts. Finding the table should be pretty easy if the base class was included in the model (either explicitly or implicitly). If the base class was not included in the model then, for the query to be meaningful there must be some class that was included in the model and that is derived from the base class. What one might be able to do is, whenever a class is included in the model take note of all of its base classes. Then when a navigation type occurs we can identify the set of classes in the model which are derived from that navigation type. These will all be in one table (due to table per hierarchy). Just execute the query on that table and select the ones that are derived from the navigation type. The comment by @ajcvickers mentions both base classes and interfaces, and the example is for interfaces. Extending the comments above, suppose that whenever a class is included in the model, we take note of all the interfaces that it implements, in addition to all the base classes. The difference is that when a navigation type is an interface, there could be multiple tables containing entities that implement that interface. Actually even within one table representing a type hierarchy there could be distinct classes that implement the interface. There are a couple of ways you could go with this. You could throw an error if there are multiple entities in the model that implement the interface. You could require configuration such as what @ajcvickers described. Or you could conceivably query all the entity types that implement the interface and return them all in one navigation list. In terms of the configuration approach, I feel that the selected class should be specifiable on a per-navigation level rather than per-model. You could easily have two navigations in different parts of the model that are represented by the same interface type but where the selected entities are meant to be different. It would be nice to be able to specify for example
The entities returned from the navigation would be all entities in the model that are derived from Blog; it is a heterogenous list. We could also specify a derived type such as [RestrictTo(typeof(RssBlog))], or for that matter any other entity type in the model that implements IBlog. One thing I wouldn't agree with would be if only one entity type could implement the interface type or be derived from the base type. That would sharply limit the use. After all, one of the main reasons for having base types or interfaces is that there can be multiple implementations or derived classes; one encapsulates re-usable functionality in the base class, or a re-usable design in the interface. This would be disabled in the entity model if there could only be one derived or implementing class. sjb P.S. Caveat emptor: I really don't know how EF works on the inside! |
This would be a very welcome feature! The casting example during configuration is extremely helpful for the time being. |
@ajcvickers Side question, if you don't mind. How is this proposed to work with An Example of how this would be in an ideal world, with DDD: // Example DDD-style entity interface defined in Project.Core
public interface IBlog
{
int Id { get; set; }
ICollection<IPost> Posts { get; }
void AddPost(IPost post);
}
// DDD-styled entity implementation defined in Project.Domain
public class Blog : IBlog
{
public int Id { get; private set; }
private HashSet<IPost> _posts { get; private set; }
public IEnumerable<IPost> Posts => _posts ?.ToList();
// Entity mutator method, missing logic ofc
public void AddPost(IPost post)
{
_posts .Add(post);
}
}
// Interface in Project.Core
public interface IAppDbContext
{
public DbSet<IBlog> Blogs {get; set;}
public DbSet<IPost> Blogs {get; set;}
}
// Concrete implementation in Project.Infrastructure
// Not sure how this part could be made possible?
public class AppDbContext : DbContext, IAppDbContext
{
public DbSet<Blog> Blogs {get; set;}
public DbSet<Post> Blogs {get; set;}
}
//Somewhere In DI config
service.AddScoped<IAppDbContext, AppDbContext>();
service.AddScoped<DbContext>(x => x.GetRequiredService<AppDbContext>());
service.AddScoped<IMyService, MyService >();
// Domain service interface Project.Core
public interface IMyService
{
IBlog GetABlog(int blogId);
}
// Domain service implementation under Project.Domain
public class MyService : IMyService
{
IAppDbContext _context;
public MyService (IAppDbContext context)
{
_context= context;
}
public IBlog GetABlog(int blogId)
{
return _context.Blogs.Find(blogId);
}
}
// Controller in web project that has no knowledge of concrete implementations
public class MyController: Controller
{
IMyService _myService;
public MyController (IMyService myService)
{
_myService = myService;
}
public IBlog GetBlog(int id)
{
return _myService .GetABlog(id);
}
}
Example Hierarchy: Core -> Domain -> Infrastructure In this example, the control is inverted. Interfaces are defined in core, and the application will rely on injected interfaces, and not reference concrete implementations. This allows a clean separation between where the contracts are defined, where they are implemented, and where they are used. With higher layer assemblies being able to provide concrete instances that are used by lower layer assemblies through their interfaces. If you took out the ability to use an Ideally one could configure EF in such a way that consumers need only know of model interfaces, and not their concrete types. EF is aware of the concrete types, but happily returns the configured interface instead (And the generic type for the IQueryable...etc is that interface, vs the concrete type). How that can be achieved though, I hope you can comment on. This is less valuable to an anemic model, but to a DDD model where domain logic & invariant guarding exists with aggregates/entities. It's extremely valuable. |
@douglasg14b An implementation of this feature would allow |
@ajcvickers Thanks for the info! Are those factories something that EF can handle internally? Ie. in a configuration of some sort telling EF that If it's external code, is that something I can read up on right now to gain a deeper understanding of how EF interfaces with factories? Similarly, if I found myself motivated, I wouldn't mind digging into EF to better understand it's source. Are there design specs for this already? It could be a fun distraction project to try and create an implementation of that... |
@douglasg14b Hard to be sure what this will look like without designing/implementing it. We don't have any design specs around this. |
Ah, I see. If I was to do some leg work on that over the next few weeks/months, would a design proposal be something that should be put into a separate issue to be discussed and picked at? I'm not necessarily making a commitment, but if my free time ever clears up it's something I'm relatively passionate about, and would like to know how you/your team would prefer a spec/design/proposal. |
@douglasg14b Yes, a proposal we can discuss would be great! However, to be transparent, we won't be able to take the PR for 3.0, and it's looking like management is only going to allow bug fixes in 3.1, so I expect the next time we will be able to release something (that is more than a preview) with new features will likely be over a year from now. 🤷♂ |
Sounds like that's giving me lots of time then! Definitely a shame that it will be a year or so, but it's understandable. I have a hell of a lot of EF source to read through anyways... |
Allow interfaces or base types to be mapped by specifying the type to use when materializing an instance of the interface or base class. For example, something like this:
Currently this can be mapped like this:
Instead, this would be mapped using something like this:
Note that there is significant overlap here with #10789. For example, it should be possible to use a factory method for creating the implementation instance, in which case the factory rather than the explicit implementation types would be in the model.
This issue is also related to #757, except that here each interface/base type is constrained to only have one implementation type at runtime.
The text was updated successfully, but these errors were encountered: