I have three classes:
public class Person
{
public string Name { get; set; }
public Guid Guid { get; set; }
}
public class Student : Person
{
public string DOB { get; set; }
}
public class Teacher : Person
{
}
I want to make string DOB as required and I am doing this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>().HasKey(d => d.Guid);
modelBuilder.Entity<Student>().Property(d => d.DOB).IsRequired(true);
modelBuilder.Entity<Teacher>();
base.OnModelCreating(modelBuilder);
}
In SQL EF Core is generating nullable column:
And it is allowing to save data when DOB is null:
MainContext mainContext = new MainContext();
mainContext.Add(new Student() { DOB = null });
mainContext.SaveChanges();
However, It is working without inheritance. Is it an EF Core issue or I am missing something in model mapping?
EDIT: I replaced DateTime? to string because the issue is not related to the type of property
Is it an EF Core issue or I am missing something in model mapping?
It is indeed EF Core issue, which also is observed in the EFC 5.0 release candidate, so it won't be addressed (except if you change the database design to utilize TPT).
First, unlike EF6, EF Core does not perform validations in general. Instead, it relies on the underlying database to do that.
Second, since the TPH database inheritance strategy stores all data in a single table, the derived entity data columns must allow null, even though they are required, otherwise you won't be able to store Teacher entity data for instance.
The combination of the two aforementioned behaviors leads to the unexpected behavior of allowing null value for the required field of a derived entity.
Therefore there is nothing you could do at mapping side (neither fluently nor with [Required] data annotation). The necessary validations should be performed by the business logic layer outside the EF Core.
Please don't suggest me to make it DateTime DOB.
I am going to suggest just that, define DOB as:
public DateTime DOB { get; set; }
EF Core uses Table-per-hierarchy here. It is clever enough to figure out that required properties on child types need to be NULL in the database because it needs to be able to store other childs (Teacher) and the base type (Person) where the required property is missing.
I think you are trying to solve a problem that is solved in EF Core itself.
I think your property will be null when no value is present and your Required attribute will behave as expected. try to add Required as an annotation on prop maybe work
Consider the following (very simplified) entities:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Answer
{
public int Id { get;set; }
public virtual User User { get; set; }
public string Text { get;set }
}
public class TeamMember
{
public int Id { get;set; }
public virtual User User { get; set; }
public string Role { get; set; }
}
In my mapper I can set the User fine, but as soon as the following code is executed (before any changes are persisted in the DB)
if (teamMembers.Select(x => x.User).Contains(currentUser))
where teamMembers is a list of TeamMembers and currentUser is a User entity loaded from the Db, then the User property of Answer is set to the previous value from the database.
My understanding is that since I haven't accessed the User property of Answer before it hasn't been loaded from the database yet, and this is what happening (it's been lazy loaded?).
I could fix it by reading the User before even setting it in the mapper but what I cannot understand is why when I access the User property of TeamMember the User property of Answer is loaded and set? Is this expected behavior since both entities are associated with the same User (i.e. in the database they have the same User_Id as a foreign key) and when loading it for TeamMembers, EF tries to be clever and populate other entities that references it and haven't been loaded yet?
The entities aren't stored/cached by the elements that have references to them but are stored with their own respective collections.
Once you loaded that User through the teamMembers reference, it was loaded... period. When you went to reference it from another element/object, it would be silly for it to go and load it again when it already has that object in memory.
This is all by design and it makes sense... if, for example, you wanted to save all these objects at the same time, it would first create the user, get an identity/key for it, then save the other objects that reference it with that key that was generated.
ref: https://msdn.microsoft.com/en-us/data/hh949853.aspx#3
"...the ObjectContext will check if an entity with the same key has
already been loaded into its ObjectStateManager. If an entity with the
same keys is already present EF will include it in the results of the
query. Although EF will still issue the query against the database,
this behavior can bypass much of the cost of materializing the entity
multiple times."
I am currently in the process of cleaning up a fairly large database. Part of the database has a relationship which is a one-to-zero-or-one mapping. Specifically:
User -> UserSettings
Not all users will have user settings, but a user setting cannot exist without the user. Unfortunately, the tables already exist. User has an PK ID. UserSettings has a PK ID and a column, User_Id_Fk which, at this point in time, is not a true FK (there is no relationship defined).
I'm in the process of fixing that and have done so from the DB perspective through SQL and have confirmed with tests. (Added the FK constraint. Added a unique constraint on User_Id_Fk.) This was all done on the UserSettings table. (Note: I am not using EF Migrations here. I have to manually write the SQL at this point in time.)
However, I now need to wire up an existing application to properly handle this new mapping. The application is using ASP.NET Core 1.0 and EF7. Here are (shortened) versions of the existing data models.
public class User
{
public int Id { get; set; }
public virtual UserSettings UserSettings { get; set; }
}
public class UserSettings
{
public int Id { get; set; }
[Column("User_Id_Fk")]
public int UserId { get; set; }
[ForeignKey("UserId")]
public virtual User User { get; set; }
}
I have this Fluent Mapping as well:
builder.Entity<UserSettings>()
.HasOne(us => us.User)
.WithOne(u => u.User)
.IsRequired(false);
When I go to run the application and access these items in the database, I get this error followed with a cryptic set of messages that has no information relating directly back to my application.:
ArgumentNullException: Value cannot be null.
Parameter name: navigation
Microsoft.Data.Entity.Utilities.Check.NotNull[T] (Microsoft.Data.Entity.Utilities.T value, System.String parameterName) <0x10d28a650 + 0x00081> in <filename unknown>, line 0
After doing research, someone had mentioned that the ID of the UserSettings class must be the same as the foreign key, like so:
public class UserSettings
{
[Key, ForeignKey("User")]
public int Id { get; set; }
public virtual User User { get; set; }
}
I don't really have this as an option as the DB is being used for other applications I have no control over at this point. So, am I stuck here? Will I just have to maintain a 1:many mapping (which could happen now, though it hasn't) and not have proper constraints for a 1:0..1 mapping?
Update
Looking at octavioccl's answer below, I tried it out without any success. However, I then removed User from the mapping in UserSettings (but I left UserId). Everything appeared to work as far as I can tell. I'm really confused what is going on here, however, and if this is even the right answer, or if I'm just getting lucky.
Remove the data annotations and try with these configurations:
builder.Entity<UserSettings>()
.Property(b => b.UserId)
.HasColumnName("User_Id_Fk");
builder.Entity<User>()
.HasOne(us => us.UserSettings)
.WithOne(u => u.User)
.HasForeignKey<UserSettings>(b => b.UserId);
From EF Core documentation:
When configuring the foreign key you need to specify the dependent
entity type - notice the generic parameter provided to HasForeignKey
in the listing above. In a one-to-many relationship it is clear that
the entity with the reference navigation is the dependent and the one
with the collection is the principal. But this is not so in a
one-to-one relationship - hence the need to explicitly define it.
The example that is presented in the quoted link (Blog-BlogImage) is pretty much the same of what are you trying to achieve.
If the solution that I show above doesn't work, then you should check if User_Id_Fk column allows null. If that is the case, change the FK property type to int?:
public class UserSettings
{
public int Id { get; set; }
public int? UserId { get; set; }
public virtual User User { get; set; }
}
Environment
Framework 4.5
Entity Framework 6 (code First)
Model
MainEntity One to many ChildEntity relationship
ChildEntity One to many recursive ChildEntity relationship
Model in code Snippet
public class MainEntity
{
public long Id { get; set; }
public virtual Collection<ChildEntity> ChildEntities { get; set; }
}
public class ChildEntity
{
public long Id { get; set; }
public MainEntity ParentMainEntity { get; set; }
public virtual Collection<ChildEntity> ChildEntities { get; set; }
}
Note : A ChildEntity can only ONE level deep again ChildEntities as childeren.
Problem
I am able to persist this model. This seems to work fine. Only one issue. When I store a ChildEntity that has a parent ChildEntity. The MainEntity_id field is NULL, only the ParentChildEntityId is set. The reason I want to have the MainEntity_Id field always set is for performance queries.
How can I force that the MAINENTITY_ID field has always a value set in the deeper level?
First:
You are giving yourself a hard time by leaving the foreign keys out of your POCO's. Adding a MainEntityId property to your ChildEntity will enable you to set the relation of new ChildEntities to a MainEntity.
Second:
var newChild =new ChildEntity();
parentEntity.ChildEntities.Add(newChild);
parentEntity.ParentMainEntity.ChildEntities.Add(newChild);
should work depending on how you have loaded the entities and which entities are or will be attached to the dbContext.
You just need to define your relationship between MainEntity and ChildEntity to be required. You may do it in two ways:
place [Required] attribute over ParentMainEntity property in ChildEntity
use fluent api. In your DbContext class override OnModelCreating method and in it place code:
modelBuilder.Entity().HasRequired(e => e.ParentMainEntity).WithMany(e => e.ChildEntities);
I would like as well to recommend you to make all your entities properties virtual. When all will be virtual then ef instead of working with your entity classes will create its own DynamicProxy classes deriving from your classes. They will provide additional tracking functionalities, they automatically change values of navigation properties if related objects changes etc. EF seems to deal much better with them. To use that functionality for newly created objects you will need to create them with context.ChildEntities.Create() method instead of using constructor. Of course as this adds constrain on your ChildEntity objects you may encounter exception during persisting data to db in SaveChanges. If the above change is the only one, that you've applied it is very probable that there is at least one ChildEntity object that do not have MainEntity object assigned to it.
I'm having problems setting up an Entity Framework 4 model.
A Contact object is exposed in the database as an updateable view. Also due to the history of the database, this Contact view has two different keys, one from a legacy system. So some other tables reference a contact with a 'ContactID' while other older tables reference it with a 'LegacyContactID'.
Since this is a view, there are no foreign keys in the database, and I'm trying to manually add associations in the designer. But the fluent associations don't seem to provide a way of specifying which field is referenced.
How do I build this model?
public class vwContact
{
public int KeyField { get; set; }
public string LegacyKeyField { get; set; }
}
public class SomeObject
{
public virtual vwContact Contact { get; set; }
public int ContactId { get; set; } //references vwContact.KeyField
}
public class LegacyObject
{
public virtual vwContact Contact { get; set; }
public string ContactId { get; set; } //references vwContact.LegacyKeyField
}
ModelCreatingFunction(modelBuilder)
{
// can't set both of these, right?
modelBuilder.Entity<vwContact>().HasKey(x => x.KeyField);
modelBuilder.Entity<vwContact>().HasKey(x => x.LegacyKeyField);
modelBuilder.Entity<LegacyObject>().HasRequired(x => x.Contact).???
//is there some way to say which key field this reference is referencing?
}
EDIT 2: "New things have come to light, man" - His Dudeness
After a but more experimentation and news, I found using a base class and child classes with different keys will not work by itself. With code first especially, base entities must define a key if they are not explicitly mapped to tables.
I left the suggested code below because I still recommend using the base class for your C# manageability, but I below the code I have updated my answer and provided other workaround options.
Unfortunately, the truth revealed is that you cannot accomplish what you seek without altering SQL due to limitations on EF 4.1+ code first.
Base Contact Class
public abstract class BaseContact
{
// Include all properties here except for the keys
// public string Name { get; set; }
}
Entity Classes
Set this up via the fluent API if you like, but for easy illustration I've used the data annotations
public class Contact : BaseContact
{
[Key]
public int KeyField { get; set; }
public string LegacyKeyField { get; set; }
}
public class LegacyContact : BaseContact
{
public int KeyField { get; set; }
[Key]
public string LegacyKeyField { get; set; }
}
Using the Entities
Classes that reference or manipulate the contact objects should reference the base class much like an interface:
public class SomeCustomObject
{
public BaseContact Contact { get; set; }
}
If later you need to programmatically determine what type you are working with use typeof() and manipulate the entity accordingly.
var co = new SomeCustomObject(); // assume its loaded with data
if(co.Contact == typeof(LegacyContact)
// manipulate accordingly.
New Options & Workarounds
As I suggested in comment before, you won't be able to map them to a single view/table anyway so you have a couple options:
a. map your objects to their underlying tables and alter your "get/read" methods on repositories and service classes pull from the joined view -or-
b. create a second view and map each object to their appropriate view.
c. map one entity to its underlying table and one to the view.
Summary
Try (B) first, creating a separate view because it requires the least amount of change to both code and DB schema (you aren't fiddling with underlying tables, or affecting stored procedures). It also ensures your EF C# POCOs will function equivalently (one to a view and one to table may cause quirks). Miguel's answer below seems to be roughly the same suggestion so I would start here if it's possible.
Option (C) seems worst because your POCO entities may behave have unforseen quirks when mapped to different SQL pieces (tables vs. views) causing coding issues down the road.
Option (A), while it fits EF's intention best (entities mapped to tables), it means to get your joined view you must alter your C# services/repositories to work with the EF entities for Add, Update, Delete operations, but tell the Pull/Read-like methods to grab data from the joint views. This is probably your best choice, but involves more work than (B) and may also affect Schema in the long run. More complexity equals more risk.
Edit I'm not sure this is actually possible, and this is why:
The assumption is that a foreign key references a primary key. What you've got is two fields which are both acting as primary keys of vwContact, but depending on which object you ask it's a different field that's the primary key. You can only have one primary key at once, and although you can have a compound primary key you can't do primary key things with only half of it - you have to have a compound foreign key with which to reference it.
This is why Entity Framework doesn't have a way to specify the mapping column on the target side, because it has to use the primary key.
Now, you can layer some more objects on top of the EF entities to do some manual lookup and simulate the navigation properties, but I don't think you can actually get EF to do what you want because SQL itself won't do what you want - the rule is one primary key per table, and it's not negotiable.
From what you said about your database structure, it may be possible for you to write a migration script which can give the contact entities a consistent primary key and update everything else to refer to them with that single primary key rather than the two systems resulting from the legacy data, as you can of course do joins on any fields you like. I don't think you're going to get a seamlessly functional EF model without changing your database though.
Original Answer That Won't Work
So, vwContact contains a key KeyField which is referenced by many SomeObjects and another key LegacyKeyField which is referenced by many LegacyObjects.
I think this is how you have to approach this:
Give vwContact navigation properties for SomeObject and LegacyObject collections:
public virtual ICollection<SomeObject> SomeObjects { get; set; }
public virtual ICollection<LegacyObject> LegacyObjects { get; set; }
Give those navigation properties foreign keys to use:
modelBuilder.Entity<vwContact>()
.HasMany(c => c.SomeObjects)
.WithRequired(s => s.Contact)
.HasForeignKey(c => c.KeyField);
modelBuilder.Entity<vwContact>()
.HasMany(c => c.LegacyObjects)
.WithRequired(l => l.Contact)
.HasForeignKey(c => c.LegacyKeyField);
The trouble is I would guess you've already tried this and it didn't work, in which case I can't offer you much else as I've not done a huge amount of this kind of thing (our database is much closer to the kinds of thing EF expects so we've had to do relatively minimal mapping overrides, usually with many-to-many relationships).
As for your two calls to HasKey on vwContact, they can't both be the definitive key for the object, so it's either a compound key which features both of them, or pick one, or there's another field you haven't mentioned which is the real primary key. From here it's not really possible to say what the right option there is.
You should be able to do this with two different objects to represent the Contact view.
public class vwContact
{
public int KeyField { get; set; }
public string LegacyKeyField { get; set; }
}
public class vwLegacyContact
{
public int KeyField { get; set; }
public string LegacyKeyField { get; set; }
}
public class SomeObject
{
public virtual vwContact Contact { get; set; }
public int ContactId { get; set; } //references vwContact.KeyField
}
public class LegacyObject
{
public virtual vwLegacyContact Contact { get; set; }
public string ContactId { get; set; } //references vwLegacyContact.LegacyKeyField
}
ModelCreatingFunction(modelBuilder)
{
// can't set both of these, right?
modelBuilder.Entity<vwContact>().HasKey(x => x.KeyField);
modelBuilder.Entity<vwLegacyContact>().HasKey(x => x.LegacyKeyField);
// The rest of your configuration
}
I have tried everything that you can imagine, and found that most solutions won't work in this version of EF... maybe in future versions it supports referencing another entity by using an unique field, but this is not the case now. I also found two solutions that work, but they are more of a workaround than solutions.
I tried all of the following things, that didn't work:
Mapping two entities to the same table: this is not allowed in EF4.
Inheriting from a base that has no key definitions: all root classes must have keys, so that inherited classes share this common key... that is how inheritance works in EF4.
Inheriting from base class that defines all fields, including keys, and then use modelBuilder to tell wich base-properties are keys of the derived types: this doesn't work, because the methos HasKey, Property and others that take members as parameters, must reference members of the class itself... referencing properties of a base class is not allowed. This cannot be done: modelBuilder.HasKey<MyClass>(x => x.BaseKeyField)
The two things that I did that worked:
Without DB changes: Map to the table that is source of the view in question... that is, if vwContact is a view to Contacts table, then you can map a class to Contacts, and use it by setting the key to the KeyField, and another class mapping to the vwContacts view, with the key being LegacyKeyField. In the class Contacts, the LegacyKeyField must exist, and you will have to manage this manually, when using the Contacts class. Also, when using the class vwContacts you will have to manually manage the KeyField, unless it is an autoincrement field in the DB, in this case, you must remove the property from vwContacts class.
Changing DB: Create another view, just like the vwContacts, say vwContactsLegacy, and map it to a class in wich the key is the LegacyKeyField, and map vwContacts to the original view, using KeyField as the key. All limitations from the first case also applies: the vwContacts must have the LegacyKeyField, managed manually. And the vwContactsLegacy, must have the KetField if it is not autoincrement idenitity, otherwise it must not be defined.
There are some limitations:
As I said, these solutions are work-arounds... not real solutions, there are some serious implications, that may even make them undesirable:
EF does not know that you are mapping two classes to the same thing. So when you update one thing, the other one could be changed or not, it depends if the objects is cached or not. Also, you could have two objects at the same time, that represents the same thing on the backing storage, so say you load a vwContact and also a vwContactLegacy, changes both, and then try to save both... you will have to care about this yourself.
You will have to manage one of the keys manually. If you are using vwContacts class, the KeyFieldLegacy is there, and you must fill it. If you want to create a vwContacts, and associate is with a LegacyObject, then you need to create the reference manually, because LegacyObject takes a vwContactsLegacy, not a vwContacts... you will have to create the reference by setting the ContactId field.
I hope that this is more of a help than a disillusion, EF is a powerfull toy, but it is far from perfect... though I think it's going to get much better in the next versions.
I think this may be possible using extension methods, although not directly through EF as #Matthew Walton mentioned in his edit above.
However, with extension methods, you can specify what to do behind the scenes, and have a simple call to it.
public class LegacyObject
{
public virtual vwContact Contact { get; set; }
public string ContactId { get; set; } //references vwContact.LegacyKeyField
}
public class LegacyObjectExtensions
{
public static vwContact Contacts(this LegacyObject legacyObject)
{
var dbContext = new LegacyDbContext();
var contacts = from o in legacyObject
join c in dbContext.vwContact
on o.ContactId == c.LegacyKeyField
select c;
return contacts;
}
}
and
public class SomeObject
{
public virtual vwContact Contact { get; set; }
public int ContactId { get; set; } //references vwContact.KeyField
}
public class SomeObjectExtensions
{
public static vwContact Contacts(this SomeObject someObject)
{
var dbContext = new LegacyDbContext();
var contacts = from o in someObject
join c in dbContext.vwContact
on o.ContactId == c.KeyField
select c;
return contacts;
}
}
Then to use you can simply do like this:
var legacyContacts = legacyObject.Contacts();
var someContacts = someObject.Contacts();
Sometimes it makes more sense to map it from the other end of the relationship, in your case:
modelBuilder.Entity<LegacyObject>().HasRequired(x => x.Contact).WithMany().HasForeignKey(u => u.LegacyKeyField);
however this will require that u.LegacyKeyField is marked as a primary key.
And then I'll give my two cents:
if the Legacy db is using LegacyKeyField, then perhaps the legacy db will be read only. In this case we can create two separate contexts Legacy and Non-legacy and map them accordingly. This can potentially become a bit messy as you'd have to remember which object comes from which context. But then again, nothing stops you from adding the same EF code first object into 2 different contexts
Another solution is to use views with ContactId added for all other legacy tables and map them into one context. This will tax performance for the sake of having cleaner context objects, but this can be counteracted on sql side: indexed views, materialized views, stored procs, etc. So than LEGACY_OBJECT becomes VW_LEGACY OBJECT with CONTACT.ContactId brought over, then:
modelBuilder.Entity<LegacyObject>().ToTable("VW_LEGACY_OBJECT");
modelBuilder.Entity<LegacyObject>().HasRequired(x => x.Contact).WithMany().HasForeignKey(u => u.ContactId);
I personally would go with creating "mapper views" with CustomerId on legacy tables, as it's cleaner from c# layer perspective and you can make those views look like real tables. It is also difficult to suggest a solution without knowing what exactly is the scenario that you have a problem with: querying, loading, saving, etc.