My SQL understanding is fairly basic and I come from a world of NHibernate so I'm fairly puzzled by this issue...
I have two classes:
public class UserProfile
{
public UserProfile() { }
[Key]
public int UserId { get; set; }
public string UserName { get; set; }
public SerialNumber Serial { get; set; }
// Other properties
}
and
public class SerialNumber
{
public SerialNumber()
{
// My code that creates a unique Code
}
[Key]
public string Code { get; set; }
public UserProfile User { get; set; }
// Other Properties
}
I have this
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Other modelBuilder stuff
modelBuilder.Entity<UserProfile>()
.HasOptional(s => s.Serial)
.WithRequired(u => u.User);
}
After I run Add-Migration I get this:
public override void Up()
{
DropForeignKey("dbo.UserProfile", "Serial_Code", "dbo.SerialNumbers");
DropIndex("dbo.UserProfile", new[] { "Serial_Code" });
RenameColumn(table: "dbo.SerialNumbers", name: "Serial_Code", newName: "User_UserId");
AddForeignKey("dbo.SerialNumbers", "User_UserId", "dbo.UserProfile", "UserId");
CreateIndex("dbo.SerialNumbers", "User_UserId");
}
public override void Down()
{
// Inverse of above
}
After I run Update-Database I get an error:
"Either the parameter #objname is ambiguous or the claimed #objtype (COLUMN) is wrong."
I can tell by the code generated by Add-Migration that something is wrong as my db table doesn't even have a column by the name of "Serial_Code"
I need Code to be my Key as EntityFramework doesn't have an option to enforce a column to be Unique except to make it a key.
How do I make Serial to be an optional property of UserProfile and User in SerialNumber to be automatically set when it's added to UserProfile?
By default, in one-to-one relationships, Entity Framework requires the foreign key on the dependent to also be the primary key. This is likely due to the fact that Entity Framework only sets unique constraints on primary keys (or rather the unique constraint is there by virtue of it being a primary key). On the level of the database, the one-to-one is not mapped onto both sides of the relationship, i.e. there's not a foreign key created on both tables. This makes sense when you think about how referential integrity works: if one side of the relationship is removed, it would cause database disintegrity for the other side, resulting in a sort of endless loop.
So, the foreign key is added to the dependent, which in the case of a one-to-zero-to-one like you have here is the end with the required navigation property: your SerialNumber model. That means that to truly create a one-to-one and not a one-to-many, your key on SerialNumber would need to be User_UserId (the auto-generated foreign key column, since you didn't manually specify a foreign key property). To set it up this way, your models would look like:
public class UserProfile
{
[Key]
public int UserId { get; set; }
public SerialNumber Serial { get; set; }
...
}
public class SerialNumber
{
[Key]
[ForeignKey("User")]
public int UserId { get; set; }
public UserProfile User { get; set; }
...
}
Or with Fluent API:
modelBuilder.Entity<SerialNumber>()
.HasRequired(m => m.User)
.WithOptional(m => m.Serial);
(You would need to leave off the [Key] data annotation, so that EF can make the foreign key, they primary key for the table.)
That leaves you without a unique constraint on your Code property, though. You can of course manually set this constraint on your table, or if you're not using automatic migrations, you can alter your migration to create the constraint:
CreateIndex("dbo.SerialNumbers", "Code", unique: true);
The problem here is that Entity Framework will not handle unique validation for you. So, you'll need to maintain integrity manually within your code (i.e., try to lookup a SerialNumber with the Code you're about to save, and only save it if you don't get anything back from the query).
It's kind of a pain, I know, but for the moment, that's all you can do. Hopefully, EF will finally address uniqueness in a future release.
Related
I have a situation where an entity has a list of children that are inactive. Additionally, they have another "current/active" sub entity of the same type.
The following would be the ideal modeling, but I cannot figure out how to this with Entity Framework Core:
public class Customer
{
public Application CurrentApplication { get; set; }
public List<Application> PastApplications { get; set; }
}
public class Application
{
public bool IsCurrent { get; set; }
public Customer Customer { get; set; }
}
In the past, I've typically modeled it as so:
public class Customer
{
public Application CurrentApplication => AllApplications.Single(a => a.IsCurrent);
public List<Application> PastApplications => AllApplications.Where(a => !a.IsCurrent);
public List<Application> AllApplications { get; set; }
}
public class Application
{
public bool IsCurrent { get; set; }
public Customer Customer { get; set; }
}
However, I feel that this could lead to the possibility of another Application incorrectly being set as IsCurrent, thus breaking the .Single().
What's the suggested way to accomplish this from a DDD perspective? If that doesn't match up with what EF Core can do, what is a good practical suggestion?
I don't think that this is a DDD problem, rather a how to design a relational DB model and how to use EF Core question.
First you need to decide what is the relationship between Customers and Applications:
One-to-Many, that is, a Customer may have zero or more Applications, but an Application belongs to exactly one Customer. This scenerio is also called a master-detail relationship. The entity on the Many side (Application in this case) stores a reference (called a foreign key) to its owner (Customer).
Many-to-Many, that is, a Customer may have zero or more Applications, but an Application may belong to zero or more Customers, as well. This scenario is modelled using an extra "join" table (usually named something like CustomerApplication) so the problem is resolved to two One-to-May relationships in the end.
If there is only one active Application at a given time (per customer), the active application can be modelled using a One-to-One (Zero-to-One, to be precise) relationship between Customer and Application (with a foreign key on Customer's side). It can also be modelled using a flag field on Application as you tried but that's not as error-proof as a foreign key (but may have better performance, though).
The code you posted resembles rather a One-to-Many scenario, so I show an example for that case. Understanding the following, you can easily change it Many-to-Many if desired.
First let's define the entities:
public class Customer
{
public int Id { get; set; }
public int? CurrentApplicationId { get; set; }
public Application CurrentApplication { get; set; }
public ICollection<Application> Applications { get; set; }
}
public class Application
{
public int Id { get; set; }
public Customer Customer { get; set; }
}
The single interesting part is int? CurrentApplicationId. We need to explicitly define the foreign key for our Zero-to-Many relationship (more on this later). The nullable int (int?) tells EF that this field can be NULL.
EF is usually able to figure out the relationship mappings but in this case we need to explain them to it explicitly:
class DataContext : DbContext
{
// ctor omitted for brevity
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>(customer =>
{
customer.HasMany(entity => entity.Applications)
.WithOne(relatedEntity => relatedEntity.Customer)
.OnDelete(DeleteBehavior.Cascade);
customer.HasOne(entity => entity.CurrentApplication)
.WithOne()
.HasForeignKey<Customer>(entity => entity.CurrentApplicationId);
});
}
public DbSet<Application> Applications { get; set; }
public DbSet<Customer> Customers { get; set; }
}
What's going on in the OnModelCreating method is called fluent API configuration. This topic and conventions is a must to understand and control how EF maps the entities to DB tables.
Based on the mapping EF is able to generate (see code-first migrations) the following DB schema:
CREATE TABLE [Customers] (
[Id] INTEGER NOT NULL
, [CurrentApplicationId] bigint NULL
, CONSTRAINT [sqlite_master_PK_Customers] PRIMARY KEY ([Id])
, FOREIGN KEY ([CurrentApplicationId]) REFERENCES [Applications] ([Id]) ON DELETE RESTRICT ON UPDATE NO ACTION
);
CREATE UNIQUE INDEX [IX_Customers_CurrentApplicationId] ON [Customers] ([CurrentApplicationId] ASC);
CREATE TABLE [Applications] (
[Id] INTEGER NOT NULL
, [CustomerId] bigint NULL
, CONSTRAINT [sqlite_master_PK_Applications] PRIMARY KEY ([Id])
, FOREIGN KEY ([CustomerId]) REFERENCES [Customers] ([Id]) ON DELETE CASCADE ON UPDATE NO ACTION
);
CREATE INDEX [IX_Applications_CustomerId] ON [Applications] ([CustomerId] ASC);
Exactly what we wanted.
Now, how you query the active and inactive applications in this configuration? Something like this:
var customerId = 1;
using (var ctx = new DataContext())
{
var currentApplication = (
from customer in ctx.Customers
where customer.Id == customerId
select customer.CurrentApplication
).FirstOrDefault();
var pastApplications =
(
from customer in ctx.Customers
from application in customer.Applications
where customer.Id == customerId && customer.CurrentApplication != application
select application
).ToArray();
}
I suggest you to read through the acticles to be found here to get familiar with EF Core.
As for relational DB modelling this site seems a useful resource.
Using EFCore, the following would work:
public class Customer
{
[Key]
public int ID {get; set;}
//other properties
//Navigation Property
public virtual ICollection<Application> Applications{ get; set; }
}
public class Application
{
[Key]
public int ID {get; set;}
[ForeignKey("Customer")]
public int CustomerID{get; set;}
public DateTime ApplicationDate{get; set}
//other properties
public bool IsCurrent { get; set; }
}
I am assuming your are using Code First, so this will be the correct way to do your mappings.
After migrating and updating the context, you could use some backend code to always ensure that your most recent application returns as IsCurrent.
You would can then select your current application as follows:
private yourContext _context;
var yourContext = _context.Customers
.Include(m=>m.Application)
.Where(m=> m.isCurrent==false)
.Single(a=>a.);
return yourContext;
You would of course have to set up your constructor with the context etc
What is wrong in my code that i get below error:
Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values
Code:
Class Food:
public class Food
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public short Id { get; set; }
//some Property
public string Name { get; set; }
public virtual ICollection<Person> Persons { get; set; }
}
Class Person:
public class Person
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
//some Property
public string FirstName { get; set; }
[ForeignKey("BestFoodId")]
public Food BestFood { get; set; }
public short BestFoodId { get; set; }
public virtual ICollection<Food> FavoriteFoods { get; set; }
}
Seed Method:
protected override void Seed(MyContext context)
{
Food food1 = new Food() { Name = "foo1" };
Food food2 = new Food() { Name = "foo2" };
Food food3 = new Food() { Name = "foo3" };
context.Persons.AddOrUpdate(new Person()
{
FirstName = "Jack",
BestFood = food2,
FavoriteFoods = new List<Food>() { food1, food2, food3 }
});
}
Cause of the error: confused associations
This happens because Entity Framework, by convention, assumes that the inverse property of Person.BestFoodId is Food.Persons. Stated differently: Person.BestFood and Food.Persons are assumed to be the two ends of a one-to-many association, having Person.BestFoodId as foreign key.
You can verify that by adding an [InverseProperty] attribute to BestFood:
public class Person
{
...
[ForeignKey("BestFoodId")]
[InverseProperty("Persons")]
public Food BestFood { get; set; }
...
}
This causes the same error.
This error --no valid ordering-- always indicates a chicken-and-egg problem. In your case, EF tries to insert the foods, which need the generated Id of the inserted person as foreign key, while the inserted person needs the generated Id of the inserted foo2 food.
Solution: explicitly mapped association
In reality, Person and Food have two associations:
1-n: Food can be BestFood of n people.
n-m: n Foods can be the FavoriteFoods of m people.
In your model, BestFood doesn't have an inverse property, which could have been something as ...
public virtual ICollection<Person> BestFoodOf { get; set; }
... but it isn't necessary and because it's missing, it obscures how EF infers the associations.
You can fix this by explicitly mapping the associations, for instance in the OnModelCreating override of your DbContext subclass:
modelBuilder.Entity<Person>()
.HasRequired(p => p.BestFood)
.WithMany() // No inverse property
.HasForeignKey(p => p.BestFoodId)
//.WillCascadeOnDelete(false)
;
modelBuilder.Entity<Person>()
.HasMany(p => p.FavoriteFoods)
.WithMany(f => f.Persons)
.Map(m => m.MapLeftKey("PersonId")
.MapRightKey("FoodId")
.ToTable("PersonFavoriteFood"));
I have commented out WillCascadeOnDelete(false). You either have to add this line, or add ...
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
... to prevent multiple cascaded delete paths (a SQL Server restrictions).
Having this in place, EF knows how to determine a valid ordering for the inserts: it will will first insert the foods, then insert the person (using the generated foo2 Id as foreign key) and then the junction records in the PersonFavoriteFood table.
Looks like you have a circular dependency.
Answers are here:
Unable to determine a valid ordering for dependent operations
Unable to determine a valid ordering for dependent operations?
Entity Framework Code First Circular Dependices
Entity Framework 4: inheritance and Associations
Entity Framework Circular Reference
Code First Circular Reference Foreign Key Configuration
How to configure a self referencing object in entity framework
Optional improvements:
You should declare your navigation property as virtual!
If you are using C# 6.0 or above, change your [ForeignKeyAttribute] Data Annotation definition to [ForeignKey([nameof(BestFoodId))] to avoid errors with hard coded property names. nameof is a really cool compiler feature! :)
At first I created a Person model which only contained the basic properties:
[Table("SGDB_Persons")]
public class Person {
public int Id { get; set; }
[Required]
public string Firstname { get; set; }
[Required]
public string Lastname { get; set; }
[Required]
public Department Department { get; set; }
[Required]
public SourceType SourceType { get; set; }
After I noticed I'm missing something I've added a new PersonData Property:
[Required]
public PersonData PersonData { get; set; }
Unfortunately EF won't update the Database at all - PersonData which at first contained an object of type Person got updated so there is no Person property anymore. On the other hand, EF does not create a new Column for PersonData_Id.
Additionally the ID column is not auto-incrementing (all other table's Id Column do). What's confusing me is the following Constraing which gets created inside my Person table:
CONSTRAINT [FK_dbo.SGDB_Persons_dbo.SGDB_PersonData_Id] FOREIGN KEY ([Id]) REFERENCES [dbo].[SGDB_PersonData] ([Id])
I tried everything (at least I think so). I dropped all tables / the whole database manually, reinstalled EF, executed manual migrations but nothing seems to work.
I think it's this problem which causes me to not be able to seed my database with the following code:
protected override void Seed(PersonContext context) {
base.Seed(context);
var dep = new DepartmentContext().Departments.First();
var status = new Status("Test");
var persondata = new PersonData(status);
context.Status.Add(status);
context.PersonData.Add(persondata);
for (int i = 0; i < 10; i++) {
var person = new Person {
Firstname = $"TestPersonFirstname{i}",
Lastname = $"TestPersonLastname{i}",
SourceType = COM.SourceType.Manual,
Department = dep,
PersonData = persondata
};
context.Persons.Add(person);
}
context.SaveChanges();
}
Everytime this code get's executed I'm getting an Exception:
The member with identity 'SGDB.DAL.Contexts.Person_Department' does not exist in the metadata collection. Parameter name: identity.
I don't know if both problems are related to the same problem but both need to be resolved :)
Thanks in advance!
Update 1
My Solution is divided into a few different Projects:
BLL, DAL, COM, UI
DataContexts are located inside the DAL project, Models inside the COM project.
Department Model:
[Table("SGDB_Departments")]
public class Department {
public int Id { get; set; }
[Required]
public string Costcenter { get; set; }
[Required]
public string Abbreviation { get; set; }
public string Description { get; set; }
public string FullDepartmentName {
get {
return $#"{Division.Abbreviation}\{Abbreviation}";
}
}
[Required]
public virtual Division Division { get; set; }
}
PersonData Model:
[Table("SGDB_PersonData")]
public class PersonData {
public PersonData(Status status) {
Status = status;
}
public int Id { get; set; }
public DateTime Limit { get; set; }
public Person Responsible { get; set; }
[Required]
public Status Status { get; set; }
}
The Person table (as you can see) has got a Department_Id column (EF inserted automatically).
Clarification
A Person object contains a PersonData object as additional Information for this Person. A Person may / may not has a Responsible Person (so PersonData.Responsible is not a Navigation Property to the Parent Person).
Additionaly if possible I don't want to have a foreign Key inside the PersonData table.
As I figured out I'd have to modify
modelBuilder.Entity<Person>()
.HasRequired(e => e.PersonData)
.WithRequiredPrincipal(e => e.Responsible)
.WillCascadeOnDelete(true);
to
modelBuilder.Entity<Person>()
.HasRequired(e => e.PersonData)
.WithMany()
.WillCascadeOnDelete(true);
I'll try this and report in if it solved my problem.
Update 2
The member with identity 'SGDB.DAL.Contexts.Person_Department' does not exist in the metadata collection.
Your model defines one-to-one relationship between Person and PersonData with later being required and the former - optional. EF always uses the required side of the one-to-one relationship as principal and optional part as dependent. Hence it thinks PersonaData is the principal and Person - dependent and reflects that in database table design.
You need the opposite and also both sides being required. When both sides are required or optional, EF cannot automatically derive the principal/dependent side and there is no way to specify that via data annotations (attributes), so you need a fluent API setup.
Override your DbContext OnModelCreating and add something like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasRequired(e => e.PersonData)
.WithRequiredPrincipal(e => e.Responsible)
.WillCascadeOnDelete(true);
base.OnModelCreating(modelBuilder);
}
What it does is to tell EF that both sides of the Person->PersonData relationship are required and Person is the principal. This should make again your Person.Id column auto-increment and should resolve the person-data part of the problem.
The other thing I've noticed is this line:
var dep = new DepartmentContext().Departments.First();
while all other parts of the same procedure are using a variable called context. This might/might not be a problem, just check it out.
UPDATE: From the clarification in the updated question it turns out you have two relationships between Person and PersonData, so you need separate configuration for each of them like this:
modelBuilder.Entity<Person>()
.HasRequired(e => e.PersonData)
.WithRequiredPrincipal()
.WillCascadeOnDelete(true);
modelBuilder.Entity<PersonData>()
.HasOptional(e => e.Responsible)
.WithOptionalDependent() // or WithMany()
.WillCascadeOnDelete(false);
Please note that there is no way to not introduce additional FK column in the PersonData table. It's needed to represent the Responsible relation, so you'll end up with a table column called Responsible_Id.
I have the following data-model in Entity Framework 6.1.3:
using System.Data.Entity;
public class Student
{
public int Id { get; set; }
public virtual Contact Contact { get; set; }
}
public class Contact
{
public int Id { get; set; }
public virtual Student Student { get; set; }
}
public class MyContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder builder)
{
builder.Entity<Contact>()
.HasOptional(x => x.Student)
.WithOptionalDependent(x => x.Contact)
.WillCascadeOnDelete(true);
}
}
public static class Program
{
private static void Main()
{
Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());
using (var context = new MyContext())
context.Database.Initialize(force: true);
}
}
When I launch this code, I get exactly the right table structure I am aiming for:
dbo.Contacts
Id (PK)
Student_Id (FK, NULL, CASCADE ON DELETE)
dbo.Students
Id (PK)
However, now I would like to add the Student_Id property to be available in the Contact entity. So I can read the Student_Id without needing to join the other table through .Student.Id navigation.
If I add the property to the Contact entity, I end up either with two columns Student_Id and Student_Id1, or I end up with an error message saying Each property name in a type must be unique..
The column is already in the database, all I need is to have it in the entity as well, why is it so much trouble? Is there a solution?
I managed to get a response from the Entity Framework Program Manager after asking on GitHub.
Unfortunately this is a limitation of EF6. You can not have a foreign key property in a one-to-one relationship, unless it is also the primary key property. This is essentially because EF6 doesn't support alternate keys/unique indexes, so you can't enforce that a non-primary key property is unique. The fact that you can do it when the foreign key property isn't in the entity is a bit of a quirk... but obviously not something we would remove 😄.
BTW alternate keys (and therefore this scenario) is supported in EF Core.
– Rowan Miller #
https://github.com/aspnet/EntityFramework6/issues/159#issuecomment-274889438
If you want to declare the FK property in the dependent entity in an one to one relationship, I'm afraid you must use it as a PK too. EF Code First requires that PK of the dependent entity must be FK of the relationship too:
public class Contact
{
[Key,ForeignKey("Student")]
public int StudentId { get; set; }
public virtual Student Student { get; set; }
}
But I think this is not what you are looking for. So, I think you have three options here:
You preserve your current relationship configuration.
Create an authentic one to one relationship.
Create an one to many relationship
By my experience the last one is the most adjusted to what are you trying to achieve (but that is my opinion). In this case you can work with the Fk property as you want, the only is you need to change the Contact navigation property on Student by a collection (or omit this nav. property and create an unidirectional relationship):
public class Student
{
public int Id { get; set; }
public virtual ICollection<Contact> Contacts { get; set; }
}
The configuration would be this way:
builder.Entity<Contact>()
.HasOptional(x => x.Student)
.WithMany(x => x.Contacts)
.HasForeignKey(x => x.StudentId)
.WillCascadeOnDelete(true);
Update
A fourth option could be create two unidirectional relationships:
builder.Entity<Contact>()
.HasOptional(x => x.Student)
.WithMany()
.HasForeignKey(x => x.StudentId)
.WillCascadeOnDelete(true);
builder.Entity<Student>()
.HasOptional(x => x.Contact)
.WithMany()
.HasForeignKey(x => x.ContactId)
.WillCascadeOnDelete(true);
But this option breaks the real relation between the two tables.
I'm using EntityFramework via DbContext and an Exisiting Database.
When I Add an Order entity to my context and call SaveChanges(), I'm encountering an exception of: "A dependent property in a ReferentialConstraint is mapped to a store-generated column. Column: OrderId".
I believe this is happening because of the composite key on my OrderAddress table and I'm hoping there is a way around it...I don't want to create an IDENTITY on that table.
Here are my entities, simplified...
// OrderId is an IDENTITY PK
public class Order
{
public int OrderId { get; set; }
public IList<OrderAddress> Addresses { get; set; }
public int Total { get; set; }
}
// PK on this table is composite key of OrderId and OrderAddressTypeId
public class OrderAddress
{
public int OrderId { get; set; }
public int OrderAddressTypeId { get; set; }
public string Address { get; set; }
}
Here is my Context, simplified...
public class StoreContext : DbContext
{
DbSet<Order> Orders { get; set; }
DbSet<OrderAddress> OrderAddresses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Set the Identity for Order
modelBuilder.Entity<Order>()
.Property(x => x.OrderId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
// Set composite key for Order Address
modelBuilder.Entity<OrderAddress>()
.HasKey(x => new { x.OrderId, x.OrderAddressTypeId });
}
}
NOTE: I've looked at the other SO questions that are similar and tried the solutions listed with no success. I've verified that my foreign keys are setup correctly in the database. What's different about my question is the use of the composite key.
Thanks in advance for the help.
UPDATE:
This ended up not being related to the composite key at all. There was an additional line in my Context OnModelCreating method that required a child entity, OrderSummary, which is based on a View in my database. The line looked like this...
modelBuilder.Entity<OrderSummary>().HasRequired(x => x.Order).WithRequiredPrincipal(x => x.OrderSummary);
I had never intended for OrderSummary to be a required principal of Order. Changing it to the following fixed the problem...
modelBuilder.Entity<OrderSummary>().HasRequired(x => x.Order);
Unfortunately, the error message from EF was not very specific and lead me on a wild good chase.
Thanks for looking.
This error says that some OrderId property (the exception should contain information about the entity or relation where this happens) is mapped as store generated = it has DatabaseGeneratedOption set to Identity or Computed. If the issue is related to OrderAddress entity try to add this to your mapping definition:
modelBuilder.Entity<OrderAddress>()
.Property(x => x.OrderId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);