I have following model, which (quite rightfully) does not work with Entity Framework Core. My problem is that I don't see an elegant solution right now, but I am sure this is a simple and quite generic construct, so I would appreciate a kick in the right direction.
Please see comments in the code for additional requirements that I have.
public abstract class Parent // requirement: must stay abstract
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Father : Parent
{
public List<Book> Books { get; set; }
}
public class Mother : Parent
{
public List<Book> Books { get; set; }
}
public class Book
{
public int Id { get; set;}
public string Title { get; set; }
public Parent Owner { get; set; } // Requirement: single Owner property; can be of Mother or Father Type
}
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base(options)
{
}
public DbSet<Mother> Mothers { get; set; }
public DbSet<Father> Fathers { get; set; }
public DbSet<Book> Books { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Father>()
.HasMany(s => s.Books)
.WithOne(s => (Father)s.Owner); // This was just an try
builder.Entity<Mother>()
.HasMany(s => s.Books)
.WithOne(s => (Mother)s.Owner); // This was just an try
builder.Entity<Book>()
.HasKey(t => t.Id);
}
}
Add-Migration does not go though with the message:
The corresponding CLR type for entity type 'Parent' cannot be instantiated, and there is no derived entity type in the model that corresponds to a concrete CLR type.
I thought so far of these potential solutions, but none qualifying:
Making Parent an Entity and moving the List<Book> Books there => No go
Giving up on navigation from Book to Owner => also No go
Splitting the Book class into two separate classes (e.g. FemaleBook & MaleBook) and managing two Entities instead of one. Very undesirable and leading to effectively doubling the code of the app => No go
A generic class? Book<Father>, Book<Mother>. Not sure, but I think that would also end up with two distinct Entities as of 3.
Something else you can help me with :-)
Remark: All classes have domain behavior methods which were omitted here for brevity. Father's a Mother's are unique (mix of override of Parent's and newly introduced).
PS: The app I am working on has nothing to do with Mothers, Fathers and Books. Just tried to provide minimum code example.
Many thanks for help.
Related
I am back trying to get into the .NET again, I have been of development for the past 10 years, though I started with .NET 1.0 and now it's a bit different. I was used to the N-tier model, with ADO.NET or what i liked even better was the recordSet in ASP. I am trying to get my head around this Repository pattern, Genneric pattern and the Entity Framework.
Please have patience with my knowledge as I have only been back for the past month. I am building a product / order service, just for fun, trying to de-couple it both to use as microservice and MVC or WinForms if I would like. I just want to learn the proper way of doing this.
The problem is that I do not get nested objects and I do understand this, maybe I need to properly join them together, but then again, why would I then use the EF if I don't get this for free? I understand you get all the modelling from the framework etc, but you get my point.
I have two models
Orders Model with the foreign key pointed out - which in my world would map automatically
public class Orders
{
[Key]
public int OrderId { get; set; }
public int ProductId { get; set; }
[ForeignKey("ProductId")]
public IEnumerable<Product> Product { get; set; }
public DateTime Datetime { get; set; }
}
Product Model
public class Product
{
[Key]
public int ProductId { get; set; }
public string ProductName { get; set; }
public string Description { get; set; }
public bool IsActive { get; set; }
public decimal Price { get; set; }
}
I have a Generic repository interface
public interface IGenericRepository <T> : IDisposable where T : class
{
IEnumerable<T> GetAllRecords();
IEnumerable<T> FindRecord(Expression<Func<T,bool>> predicate);
T GetRecordById(int objId);
void AddRecord(T obj);
void DeleteRecord(T obj);
void UpdateRecord(T obj);
}
I Implement this interface through
public class GenericRepository<T> : IGenericRepository<T>, IDisposable where T : class
{
private readonly DBContext _context;
private readonly DbSet<T> _DbSet;
public GenericRepository(DBContext context)
{
this._context = context;
this._DbSet = this._context.Set<T>();
}
public IEnumerable<T> GetAllRecords()
{
return _DbSet.ToList();
}
public IEnumerable<T> FindRecord(Expression<Func<T, bool>> predicate)
{
throw new NotImplementedException();
}
public T GetRecordById(int objId)
{
return _DbSet.Find(objId);
}
public void AddRecord(T obj)
{
_DbSet.Add(obj);
}
public void DeleteRecord(T obj)
{
_DbSet.Remove(obj);
}
public void UpdateRecord(T obj)
{
_DbSet.Attach(obj);
_context.Entry(obj).State = EntityState.Modified;
}
public void Save()
{
throw new NotImplementedException();
}
void IDisposable.Dispose()
{
throw new NotImplementedException();
}
}
And last i have the UnitOfWork with an interface that I implement through
public class UnitOfWork : IUnitOfWork
{
private DBContext _context;
// public ProductRepository productRepository { get; private set; }
//public OrderRepository OrderReposity { get; private set; }
public IGenericRepository<Product> productRepository { get; set; }
public IGenericRepository<Orders> OrderRepository { get; set; }
public UnitOfWork(DBContext context)
{
this._context = context;
this.productRepository = new GenericRepository<Product>(this._context);
this.OrderRepository = new GenericRepository<Orders>(this._context);
}
public void SaveChanges()
{
_context.SaveChanges();
}
}
In the WebAPI controller I call the unitOfWork through
[Route("api/[controller]")]
[ApiController]
public class OrdersController : ControllerBase
{
public readonly UnitOfWork UoW;
public OrdersController(IUnitOfWork _uow)
{
this.UoW = _uow as UnitOfWork;
}
And pointing to the API method GET / Orders
// GET: api/<OrdersController>
[HttpGet]
public IEnumerable<Orders> Get()
{
return UoW.OrderRepository.GetAllRecords();
}
It works like a charm, I think its a good way to implement this. I can easily create another application to use the back-end, I can mock and test this pretty well. Everything is in 4 different projects in the solution.
But the problem is the returns a null on the products objects.
Feel free to give me all feedback you can, how I should build this if my solution is not preferred or if I am doing it too "de-coupled".
Thanks for a great forum and a great inspiration for learning
Best regards
Martin
I'm assuming you are using .NET Core here, so if you are not, please leave a comment and I'll re-write the solution to fit your needs. =)
First of all I believe that you have a miss modeled your data classes since um have a 1-n between products x orders, the way it was build means that a product can only be related by one order which is not true, so we need a table to represent the n-n relation between these two entities and a little bit changes in the original 2 ones to support the mapping between them.
Product.cs
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public string Description { get; set; }
public bool IsActive { get; set; }
public decimal Price { get; set; }
public List<OrderProduct> OrderProducts { get; set; }
}
Order.cs
public class Order
{
public int OrderId { get; set; }
public DateTime Datetime { get; set; }
public List<OrderProduct> OrderProducts { get; set; }
}
OrderProduct.cs (the one that will ties things up)
public class OrderProduct
{
public int OrderProductId { get; set; }
public int ProductId { get; set; }
public int OrderId { get; set; }
public Product Product { get; set; }
public Order Order { get; set; }
}
With the entities now set up we can run for the part were entity will load the related entities automatically for you =)
To Entity Framework knows what is related to what we need more than convention naming (name thing equally), entity expects for explicit configuration.
You were configuring this relations with DataAnnotations which is not wrong, but personally I prefer to work with Fluent API since DataAnnotations create a hard bound between your entity models and Entity Framework making it harder to change your architecture for a DDD model in the future for example.
So let's configure our entities. To achieve this we will create a configuration file for each entity.
ProductConfig.cs
public class ProductConfig : IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> builder)
{
// Tells entity that ProductId is your primary key
builder.HasKey(b => b.ProductId);
}
}
OrderConfig.cs
public class OrderConfig : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
// Tells entity that OrderId is your primary key
builder.HasKey(b => b.OrderId);
}
}
OrderProductConfig.cs
public class OrderProductConfig : IEntityTypeConfiguration<OrderProduct>
{
public void Configure(EntityTypeBuilder<OrderProduct> builder)
{
// Tells entity that OrderProductId is your primary key
builder.HasKey(b => b.OrderProductId);
// Configure the navigation property Order telling that an Order may be related to a bunch of OrderProducts
// setting up the OrderId as your foreign key constraint and telling Entity that is a required field (not null)
builder.HasOne(b => b.Order)
.WithMany(b => b.OrderProducts)
.HasForeignKey(b => b.OrderId)
.IsRequired();
// Configure the navigation property Product telling that a Product may be related to a bunch of OrderProducts
// setting up the ProductId as your foreign key constraint and telling Entity that is a required field (not null)
builder.HasOne(b => b.Product)
.WithMany(b => b.OrderProducts)
.HasForeignKey(b => b.ProductId)
.IsRequired();
}
}
Now we need a place to apply this configurations, so we need to create a context for your application, is a good practice to create one also =)
SalesSoftwareContext.cs
// This class inherits DbContext class, and will be your new "DbContext"
public class SalesSoftwareContext : DbContext
{
// During runtime this method will be called so Entity will load the configurations and knows how to handle things
protected override void OnModelCreating(ModelBuilder builder)
{
builder.ApplyConfiguration(new OrderConfig());
builder.ApplyConfiguration(new ProductConfig());
builder.ApplyConfiguration(new OrderProductConfig());
}
}
Inside GenericRepository.cs and UnitOfWork.cs replace the references for DBContext by SalesSoftwareContext and run your code!
Now when you query the Order you will have a List of OrderProduct that you will be able to interact and get the product info, for an example:
public class OrderViewModel
{
public int OrderId {get;set;}
List<OrderItemViewModel> OrderItems {get;set;}
}
public class OrderItemViewModel
{
public int ProductId {get;set;}
public string Name {get;set;}
public decimal Price {get;set;}
}
[HttpGet]
public IEnumerable<OrderViewModel> Get()
{
List<OrderViewModel> returnValue = new List<OrderViewModel>();
List<Order> orders = UoW.OrderRepository.GetAllRecords();
foreach(var order in orders)
{
OrderViewModel orderToReturn = new OrderViewModel();
orderToReturn.OrderId = order.OrderId;
orderToReturn.OrderItems = order.OrderProducts
.Select(x => new OrderItemViewModel
{
ProductId = x.Product.ProductId,
Name = x.Product.ProductName,
Price = x.Product.Price
}).ToList());
returnValue.Add(orderToReturn);
}
return returnValue;
}
I hope this helps =)
Since I don't have your 'full-code' I ended up typing in the dark, sorry for any typo or error that came along with the posted solution
It's nice to have another developer coming back to .NET world, the .NET evolved a lot in the last years, specially when .NET Core came around, but I believe that all the changes were for better.
If you have specific questions or need any guidance, leave a comment with your email so we can get in touch.
Happy Holidays!
Simply, in C# EF6, how do you map two navigation properties to the same table while keeping their result sets separate? In plain English, I have a class that I want two collections of in another class. In other words, I want two collections of the same type but with different elements. Unfortunately, EF6 seems to treat both collections the same and gives them both the same elements (every record in the table).
The best I found from dozens of StackOverflow answers was this, but it has the problem described. In this example, a Father has many Sons and many Daughters, and they each have the one Father. Ideally, both Sons and Daughters can be stored in the same table Child.
class Father
{
[Key]
public long Id { get; set; }
public virtual ICollection<Child> Sons { get; set; }
public virtual ICollection<Child> Daughters { get; set; }
}
class Child
{
[Key]
public long Id { get; set; }
public long FatherId_1 { get; set; }
public Father Father_1 { get; set; }
public long FatherId_2 { get; set; } // One for each collection???
public Father Father_2 { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Child>()
.HasRequired(e => e.Father_1)
.WithMany(e => e.Sons)
.HasForeignKey(e => e.FatherId_1);
modelBuilder.Entity<Child>()
.HasRequired(e => e.Father_2)
.WithMany(e => e.Daughters)
.HasForeignKey(e => e.FatherId_2);
}
The problem with this is, when reading the data back from the Child table, it doesn't discriminate between Sons and Daughters. That is, the Sons collection will not only contain Sons but also Daughters, and so will the Daughters collection. I might have expected EF6 to try to use a discriminator column, but it doesn't.
Question: How do you map two navigation properties to the same table and still be able to read its records back into their corresponding navigation properties? Or, is the example correct, and my problem is elsewhere? Or, is this not possible, and they need to be mapped to their own tables (with identical schemas).
I am a little confused, with your explanation. But I found some code for what I understand:
For "It's one model "Child" that needs to be split into two collections" :
modelBuilder.Entity<Child>()
.Map(m =>
{
m.Properties(t => new { t.Id /*other props*/ });
m.ToTable("Sons");
})
.Map(m =>
{
m.Properties(t => new { t.Id /*other props*/});
m.ToTable("Daughters");
});
I think you don't require separate mappings altogether to bifurcate same FatherId for Son and daughter. The better solution is to make a unique identifier in Child Table like enum as previous answer suggests or add a Gender field in your Child model and keep only one mapping of Father model. (I don't know your exact requirements, but Father, Child can also be generalized in one super class human or person in strict Object Oriented terms).
But since I am not sure of your requirements, if you really want to continue with your mapping then the solution I propose is this.
class Father
{
[Key]
public long Id { get; set; }
public int SonId{get;set;}
public int DaughterId{get;set;}
[ForeignKey("SonId")]
public virtual Child Child_Son{get;set;}
[ForeignKey("DaughterId")]
public virtual Child Child_Son{get;set;}
}
class Child
{
[Key]
public long Id { get; set; }
public string Gender{get;set;}
}
Explanation:
Two foreign keys each for Son and Daughter of same Child class in Father class will easily help you achieve separate collections using basic Linq or any sql query. Moreover, This preserves the rule "If parent exists only then child exists".
I found a way to solve this problem by using a domain object backed by a state object. Basically, you use a state object to store the data the way EF likes, and your domain object is what exposes that data to the rest of your application. For example:
public class Father
{
//--- Constructor ---
internal Father(FatherState state)
{
State = state;
}
//--- Properties ---
public long Id => State.Id;
public IList<Child> Sons => Children.Where(child => child.Type == ChildType.Son).ToList().AsReadOnly();
public IList<Child> Daughters => Children.Where(child => child.Type == ChildType.Daughter).ToList().AsReadOnly();
//--- Methods ---
public void AddChild(Child child)
{
State.Children.Add(child);
}
public void RemoveChild(Child child)
{
State.Children.Remove(child);
}
}
internal class FatherState
{
[Key]
public long Id { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
public class Child
{
[Key]
public long Id { get; set; }
public long FatherId { get; set; }
public Father Father { get; set; }
public ChildType Type { get; set; }
}
public enum ChildType
{
Son,
Daughter
}
Of course, this can only be used with the repository pattern because it has to translate the FatherState object provided by EF into the Father object consumed by the application.
I think the real solution is to switch to a fully featured ORM like NHibernate. EF is far too underdeveloped, and that's only gotten worse with .NET Core. I don't even see how it's possible to do proper DDD with EF. A lot of developers must be compromising on their models. NHibernate has a lot of advantages, and the only caveat I've found is it doesn't provide async methods, but there's one or two arguments to made against it, and it can be done anyway. There's also forks of NHibernate that provide them as a last resort.
What I could see in your modeling is that, the child entity needs to have property fatherId and gender. Check the code below:
public class Child
{
[Key]
public long Id {get; set;}
public string Name {get; set;}
public Gender Gender{get; set;} //Usually use Enum containing Female and Male
public long FatherId{get; set;}
public virtual Father Father {get; set;}
}
public class Father
{
public Father()
{
Children = new HashSet<Child>();
}
[Key]
public long Id {get; set;}
public string Name {get; set;}
public virtual ICollection<Child> Children{get; set;}
}
Getting child base on gender will work for what you want to do.
I modeled my m:m tables following the advice here code first many to many. Below is my model (and this is reflected in the database):
public class Catalog {
...
public virtual ICollection <CatalogItem> CatalogItems {get;set;}
public virtual ICollection <CatalogSubscriber> CatalogSubscribers {get;set;}
}
public class Item {
...
public virtual ICollection <CatalogItem> CatalogItems {get;set;}
}
public class Subscriber {
...
public virtual ICollection <CatalogSubscriber> CatalogSubscribers {get;set;}
}
public class CatalogItem{
[ForeignKey("Catalog")]
public Guid Catalog_Id { get; set; }
[ForeignKey("Item")]
public Guid Item_Id { get; set; }
public virtual Catalog Catalog { get; set; }
public virtual Item Item { get; set; }
}
public class CatalogSubscriber{
//similar to catalogitem
}
I am getting the error "invalid column Subscriber_Id" when trying to get catalogs, and I can see in the select statement that for some reason it thinks that Subscriber_Id is a column in Catalog (it isn't). I tried this solution, but to no avail. Funny thing is that CatalogItems were working perfectly, till I added the m:m on CatalogSubscribers. It doesn't seem to have a problem with CatalogItems at all. What am I doing wrong here?
Normally you don't even need the CatalogItem or CatalogSubscriber classes. EF will infer them for you, so long as the only fields are the two foreign keys.
public class Catalog {
...
public virtual ICollection<Item> Items {get;set;}
public virtual ICollection<Subscriber> Subscribers {get;set;}
}
public class Item {
...
public virtual ICollection<Catalog> Catalogs {get;set;}
}
public class Subscriber {
...
public virtual ICollection<Catalog> Catalogs {get;set;}
}
After reading both the links in your question, both answers there are correct. Except you've tried to manually create a custom cross reference table, which you don't need if it only contains the foreign key ids. In that case, you don't create the class at all, and use the Fluent API in your second link to tell EF that you have a Many to Many relationship between Catalog and Item, and another one between Catalog and Subscriber. Like this (I think -- I normally go database first out of habit):
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Catalog>().HasMany(m => m.Items).WithMany();
modelBuilder.Entity<Catalog>().HasMany(m => m.Subscribers).WithMany();
}
I think this is possible in nhiberate, but my question is about Entity Framework.
In my database model - which I cannot modify - I have redundant columns that I would like to store in different classes.
Example :
public class DateParams
{
public DateTime CreationDate { get; set; }
public DateTime ModificationDate { get; set; }
// some methods
}
public class Localization
{
public String EnglishLabel { get; set; }
public String FrenchLabel { get; set; }
// some methods
}
And then I would use them in some of my models :
public class Account // Localization && DateParams
{
public int ID { get; set; }
public String Name { get; set; }
public Localization Localization { get; set; }
public DateParams DateParams { get; set; }
}
public class Lead // DateParams only
{
public int ID { get; set; }
public String Name { get; set; }
public DateParams DateParams { get; set; }
}
What I would like to achieve is having something like this
public class LocalizationMap : EntityTypeConfiguration<Localization>
{
public LocalizationMap()
{
Property(e => e.EnglishLabel).HasColumnName("en");
Property(e => e.FrenchLabel).HasColumnName("fr");
}
}
public class AccountMap : EntityTypeConfiguration<Account>
{
public AccountMap()
{
HasKey(x => x.ID);
Property(e => e.Name).HasColumnName("Name");
HasSubMapping(new LocalizationMap());
HasSubMapping(new DateParamsMap());
ToTable("Account");
}
}
I could use inheritance to solve this, but C# does not allow multiple inheritance.
I'm not going to make you happy.
There is an EF feature called Table Splitting. As the name suggests, this allows us to map (split) one database table to multiple classes in the conceptual model. In your case, the mappings for Account would look like this:
class AccountMap : EntityTypeConfiguration<Account>
{
public AccountMap()
{
ToTable("Account");
HasKey(x => x.ID);
HasRequired(a => a.DateParams).WithRequiredPrincipal();
HasRequired(a => a.Localization).WithRequiredPrincipal();
}
}
class DateParamsMap : EntityTypeConfiguration<DateParams>
{
public DateParamsMap()
{
ToTable("Account");
}
}
class LocalizationMap : EntityTypeConfiguration<Localization>
{
public LocalizationMap()
{
ToTable("Account");
}
}
But this immediately shows the problem: the table name "Account" in the type configurations is hard coded. There's no way to reuse the satellite classes DateParams and Localization for multiple types. And, before you try, EF won't accept generics like DateParams<T>.
Which is sad, because all other options I can think of are ugly, or clunky at best:
Create subclasses of DateParams and Localization (and accompanying configurations) for any entity that needs them.
Just add the properties to all types and work with projections as much as possible (because I assume the whole point of this effort is to reduce the number of properties you're going to query).
Use one context hosting the main types without these properties and a second context hosting the satellite types (again, to help querying less properties easily). But unfortunately, you can only join the instances from both contexts in memory, i.e. LINQ to objects.
Create a third satellite class, combining both smaller classes, and use these three classes as base types.
You can achieve this by using complex types. These map to table columns named like complextypeName_propertyName but this behaviour can be changed by overwriting OnModelCreating(DbModelBuilder modelBuilder) in DbContext like described in Entity Framework - Reuse Complex Type
For your example:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.ComplexType<Localization>();
modelBuilder.Entity<Account>().Property(x => x.Localization.EnglishLabel).HasColumnName("en");
modelBuilder.Entity<Account>().Property(x => x.Localization.FrenchLabel).HasColumnName("fr");
// et cetera
}
So I'm trying to use Code First with Fluent to map a base class with one derived type where the tables schema is a Table-per-Type arrangement. Also, the derived type has a many-to-one relationship with another type that also has a composite foreign key. (The keys on these tables is unchangeable and the names match up exactly.)
Here is an example of what I'm trying to achieve in CSharp:
public class BaseType
{
public int Id;
public int TenantId;
public int Name;
}
public class DerivedType : BaseType
{
public int Active;
public int OtherTypeId;
public OtherType NavigationProperty;
}
Here is the configuration for this in the configuration classes:
public BaseTypeConfiguration()
{
ToTable("BaseTypes", "dbo");
HasKey(f => new { f.Id, f.TenantId});
Property(f => f.Id)
.HasColumnName("BaseTypeId")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
public DerivedTypeConfiguration()
{
ToTable("DerivedTypes", "dbo");
//OtherType has many DerivedTypes
HasRequired(dt=> dt.OtherTypeNavigation)
.WithMany(ot=> ot.DerivedTypes)
.HasForeignKey(dt=> new { dt.OtherTypeId, dt.TenantId});
}
From what I can tell my mapping is set up correctly (as in, I followed many tutorials and examples that had this exact situation but with a single column identifier)
When I try to query these entities the exception I get is:
The foreign key component 'TenantId' is not a declared property on type 'DerivedType'.
And when I try to explicitly declare these properties on the type using the new keyword I get an exception saying that duplicate properties exist.
Answer
Response from EF Team
This is part of a more fundamental limitation where EF doesn't support having a property defined in a base type and then using it as a foreign key in a derived type. Unfortunately this is a limitation that would be very hard to remove from our code base. Given that we haven't seen a lot of requests for it, it's not something we are planning to address at this stage so we are closing this issue.
I think this is what you are looking for:
[Table("BaseType")]
public class BaseType
{
[Key, DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)]
public int Id {get;set;}
[Key]
public int TenantId { get; set; }
public int Name { get; set; }
}
[Table("Derived1")]
public class DerivedType : BaseType
{
public int Active { get; set; }
public int OtherTypeId { get; set; }
public virtual OtherType NavigationProperty {get;set;}
}
[ComplexType]
public class OtherType
{
public string MyProperty { get; set; }
}
public class EFCodeFirstContext : DbContext
{
public DbSet<BaseType> BaseTypes { get; set; }
public DbSet<DerivedType> DerivedTypes { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<BaseType>().HasKey(p => new { p.Id, p.TenantId });
base.OnModelCreating(modelBuilder);
}
}
This is unsupported as of right now, according to their support. I had created a case for a similar stack question here and the author updated with the case results.
https://stackoverflow.com/a/14880084/1791547