I have two classes: Customer and Association.
A customer can have an association with many customers. Each association is of a defined type (Family, Friend, etc) i.e Customer A is a friend of Customer B. Customer A is related to Customer C. The type of association is defined by an enum AssociationType.
In order to create this in EF i've defined the following classes
public class Customer
{
public string FirstName {get; set;}
public string LastName {get; set;}
public virtual ICollection<Association> Associations { get; set; }
}
public class Association
{
public int CustomerId { get; set; }
public virtual Customer Customer { get; set; }
public int AssociatedCustomerId { get; set; }
public virtual Customer AssociatedCustomer { get; set; }
public AssociationType AssociationType { get; set; }
}
I've removed the Data Annotations as I was unable to get this to compile. I get the error:
"Model compatibility cannot be checked because the database does not
contain model metadata".
Does anyone have any ideas?
It happens sometimes when an error occurs during database creation. The database schema is created then - except the __MigrationHistory table. When you run your application again EF wants to check against the __MigrationHistory table if the schema is still up-to-date with the model and if that table doesn't exist it throws the exception you are having.
To fix the problem either delete the database manually or set the initializer to DropCreateDatabaseAlways<MyContext> (with Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>()) - only once. After the DB is created set it back to your original initializer.
BTW: For your model you will have to specify explicitly that Customer.Associations is related to Association.Customer, either with data annotations...
[InverseProperty("Customer")]
public virtual ICollection<Association> Associations { get; set; }
...or with Fluent API:
modelBuilder.Entity<Customer>()
.HasMany(c => c.Associations)
.WithRequired(a => a.Customer)
.HasForeignKey(a => a.CustomerId);
Thank you Slauma,
your answer got us going in the right direction.
We added the following configuration to the Association configuration:
HasRequired(x => x.AssociatedCustomer).WithMany().WillCascadeOnDelete(false);
Related
I am facing an issue in using SQLite-Net Extensions to save data in local DB in scenario where the foreign key is referencing the same entity (self-join).
Example – Employee and Manager. Every employee has a manager and a manager is also an employee. I am facing issues in saving data in such cases. It will be really helpful if you can provide some insights. Does this extension support this kind of relationship?
Yes, relationships between objects of the same class are supported, but the foreign keys and inverse properties must be explicitly specified in the relationship property attribute because the discovery system will get it wrong as there are be two relationships with the same type.
This example is extracted from the project readme:
public class TwitterUser {
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Name { get; set; }
[ManyToMany(typeof(FollowerLeaderRelationshipTable), "LeaderId", "Followers",
CascadeOperations = CascadeOperation.CascadeRead)]
public List<TwitterUser> FollowingUsers { get; set; }
// ReadOnly is required because we're not specifying the followers manually, but want to obtain them from database
[ManyToMany(typeof(FollowerLeaderRelationshipTable), "FollowerId", "FollowingUsers",
CascadeOperations = CascadeOperation.CascadeRead, ReadOnly = true)]
public List<TwitterUser> Followers { get; set; }
}
// Intermediate class, not used directly anywhere in the code, only in ManyToMany attributes and table creation
public class FollowerLeaderRelationshipTable {
public int LeaderId { get; set; }
public int FollowerId { get; set; }
}
As you can see here we have a many-to-many between Twitter users. In your case it will be a one-to-many, so you won't need the intermediate table and you'll need the foreign key (ManagerId for example) in your Person class.
I have the following domain model:
public class Campaign
{
public virtual long Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<Content> Content { get; set; }
}
public class Content
{
public virtual long Id { get; set; }
public virtual string Body { get; set; }
}
This is configured:
modelBuilder.Entity<Campaign>().HasMany(x => x.Content).WithOptional();
In my service I have the following code:
Campaign campaign = campaignRepository.GetById(id);
This loads the campaign and any associated content items into the collection which is great. The issue comes with the following code:
campaign.Name = "new value";
campaign.Content.Clear();
unitOfWork.Commit();
This does not delete the content rows from the database. It actually sets the foreign key in the content table to null for the affected rows but it does not delete the rows.
I then tried to modify the configuration to:
modelBuilder.Entity<Campaign>().HasMany(x => x.Content).WithRequired();
This would simply give me the following exception: A relationship from the 'Campaign_Content' AssociationSet is in the 'Deleted' state. Given multiplicity constraints, a corresponding 'Campaign_Content_Target' must also in the 'Deleted' state.
There must be a way to delete rows from the content collection. I must be missing something. Any help is appreciated.
You will have call the Remove method on the corresponding DbSet for each entity instance.
foreach(var content in campaign.Content)
{
dbContext.Contents.Remove(content);
}
I have the following class:
public class User
{
public Guid Id { get; set; }
public string Name { get; set; }
public Couple Couple { get; set; }
}
public class Couple
{
public Guid Id { get; set; }
public User Groom { get; set; }
public User Bride { get; set; }
}
Important points:
Bride and Groom properties are required
One-to-one relationship
In the User class, it is Couple required
DbContext in OnModelCreating
modelBuilder.Entity<User>().HasRequired(u => u.Couple).WithRequiredPrincipal();
modelBuilder.Entity<Couple>().HasRequired(u => u.Bride).WithRequiredDependent();
modelBuilder.Entity<Couple>().HasRequired(u => u.Groom).WithRequiredDependent();
But I can not be required!
All fileds are with null in the database!.
How do I get the fields in the database as not null?
If possible using the API Flient.
It should be this :
modelBuilder.Entity<User>().HasRequired(u => u.Couple).WithRequiredDependent();
modelBuilder.Entity<Couple>().HasRequired(u => u.Bride).WithRequiredDependent();
modelBuilder.Entity<Couple>().HasRequired(u => u.Groom).WithRequiredDependent();
How WithRequiredDependent Works : Configures the relationship to be required:required without a navigation property on the other side of the relationship. The entity type being configured will be the dependent and contain a foreign key to the principal. The entity
type that the relationship targets will be the principal in the relationship.
Meaning : Let's consider your first line of code here. It creates a foreign key in the entity being configured (User) making it Dependant and making the other side of the relationship (Couple) Principal
Important : Don't you think the configuration you desire will generate a deadlock? I've not tested the code above, but this configuration seems to be a deadlock to me so i'm not sure if EF would allow you to create it. User must need a Couple, and Couple must need that same user i guess.
I have the following classes:
public class CartItem
{
public long Id { get; set; }
public int Quantity { get; set; }
public Product Product { get; set; }
}
public class Product {
public long Id { get; set; }
public string Title { get; set; }
public decimal Price { get; set; }
}
I currently have the following configuration:
modelBuilder.Entity<CartItem>().HasRequired(x => x.Product).WithMany().Map(x => x.MapKey("ProductId"));
I am trying to ensure that whenever I retrieve a cartitem from the database there will be a join on the product table so I can access the product properties but not the other way around.
I basically want to be able to do:
string title = cartItem.Product.Title
using the configuration I have gives me an Object reference not set to an instance of an object exception.
Short answer: to solve your problem, make the Product property virtual.
In-depth:
First, you don't need a join to do this. EF works fine with lazy loading (you need the virtual modifier)
Second, you can fetch the Product eagerly, using the Include extension method. Example:
var cartItem = context.CartItems.Include(x => x.Product)
.Where(/*some condition*/).ToList();
...but you can't configure this to be the default behavior (nor is it a good idea usually)
Third, this is a many-to-one relationship, not one-to-one (a Product has many related CartItems)
I have to admit, the features of EF 4.1 RC Codefirst, DataAnnotations and FluentAPI are still overwhelming to me. Sometimes I really don't know what I am doing ;-) Please see the following POCOs:
public class Country
{
[Key]
public Guid ID { get; set; }
[Required]
public virtual Currency Currency { get; set; }
}
public class Currency
{
[Key]
public Guid ID { get; set; }
public virtual ICollection<Country> Countries { get; set; }
}
The general idea: Every country needs to have a currency. But a currency does not need to be assigned to a country at all.
If you let EF create the corresponding database, the relationship will be set to CASCADE DELETE by convention. In other words: if you delete a currency, the corresponding countries are deleted as well. But in my case this is not what I want.
I came up with some code in FluentAPI in order to disable CASCADE DELETE:
modelBuilder.Entity<Country>()
.HasRequired(cou => cou.Currency)
.WithOptional()
.WillCascadeOnDelete(false);
I thought this means: Every country requires a currency. And this currency might have zero, one or more countries assigned (optional). And whenever I delete a currency, the corresponding countries (if there are any) will NOT be cascade deleted.
Surprisingly the given approach will still cascade delete a country if I delete the corresponding currency. Can anybody tell me what I miss?
Firstly you've specified the currency as a required field on country, so you can't delete a currency. You'll need to remove the [Required].
Secondly, your model builder need the following:
modelBuilder.Entity<Country>()
.HasRequired(cou => cou.Currency) //note optional, not required
.WithMany(c=>c.Countries) //define the relationship
.WillCascadeOnDelete(false);
Thirdly, you need to explicitly remove the reference to the entity you are deleting from it's children:
Currency c = context.Currencies.FirstOrDefault();
c.Countries.Clear(); //these removes the link between child and parent
context.Currencies.Remove(c);
context.SaveChanges();
[EDIT]
Because I suspect there is something lost in translation find the complete code that demonstrates how no-cascading deletes would work.
public class Country{
[Key]
public Guid ID { get; set; }
public virtual Currency Currency { get; set; }
}
public class Currency{
[Key]
public Guid ID { get; set; }
public virtual ICollection<Country> Countries { get; set; }
}
public class MyContext : DbContext{
public DbSet<Currency> Currencies { get; set; }
public DbSet<Country> Countries { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder){
modelBuilder.Entity<Country>()
.HasRequired(country => country.Currency)
.WithMany(currency => currency.Countries)
.WillCascadeOnDelete(false);
}
}
class Program{
static void Main(string[] args){
Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());
using (MyContext context1 = new MyContext()){
Currency c = new Currency{ID = Guid.NewGuid()};
context1.Currencies.Add(c);
c.Countries = new List<Country>();
c.Countries.Add(new Country{ID = Guid.NewGuid()});
context1.SaveChanges();
}
using (MyContext context2 = new MyContext()){
Currency c = context2.Currencies.FirstOrDefault();
context2.Currencies.Remove(c);
//throws exception due to foreign key constraint
//The primary key value cannot be deleted
//because references to this key still exist.
//[ Foreign key constraint name = Country_Currency ]
context2.SaveChanges();
}
}
}
You will get an error on saving, because your deleting something that is a required foreign key.