Deleting rows from a collection using ef code first - ef-code-first

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);
}

Related

Two foreign keys to same primary table

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);

How can I name my Database using EF Code First?

I've got my EF Code First working exactly as expected aside from one small bit. I'm not sure how to name my Database File.
I'm using SQL CE, but I'm sure this applies to all forms of EF Code First.
Here's my DbContext
namespace MyApp.Domain.EntityFramework
{
public class DataContext : DbContext
{
//...
}
}
And when the database is created it's created as
MyApp.Domain.EntityFramework.DataContext.sdf
I'd prefer to just have it named
MyApp.sdf
Now I'm sure this is simple, but my Googling skills keep turning up examples where the database name is auto generated like mine.
http://www.hanselman.com/blog/SimpleCodeFirstWithEntityFramework4MagicUnicornFeatureCTP4.aspx
You need to specify a connection string (for example by creating a connection string named DataContext (your class name) in your config file, and set the desired name there.
I was looking to do the same. Managed to end up with this:
public class ShopDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Feature> Features { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<Subcategory> Subcategories { get; set; }
public DbSet<Information> OrderInformation { get; set; }
public ShopDbContext() : base("Shop")
{
}
}
It will name your database "Shop" so just replace what is in the base("Shop") with whatever you want to call your database. Hope this helps.

ASP.Net MVC3 conditional validation

I'm having some troubles with validation on my application.
Let's say I've the following models:
public class Company
{
public int id { get; set; }
[Required]
public String Name { get; set; }
public String Location { get; set; }
public List<Contacts> Contacts { get; set; }
}
public class Contact
{
public int id { get; set; }
[Required]
public String Name { get; set; }
[DataType(DataType.EmailAddress)]
public String Email { get; set; }
public String Telephone { get; set; }
public String Mobile { get; set; }
}
Now in my company create view I've two buttons, one to add contacts to the company, and another one to create the new company.
I detected which button was used in my controller like this (both buttons are named "button"):
[HttpPost]
public ActionResult Create(String button, FormCollection collection)
{
if(button == "AddContact")
{
AddContact(collection);
}
else
{
CreateCompany(collection);
}
}
While it's being created the object that represents the company that it's being create is stored in the session (for example HttpContext.Session["company"] = company;)
Now the problem is that if, for example, I try to add a contact without first specifying the company name, i get a validation error because the company name is required, which shouldn't happen because the user might want to add the contacts before adding the company info. Or if I try to save the company, I also get a validation error, because usually when saving the "add contact" form is empty, which means that the contact name (which is required as well) was not specified.
What I want to know is that if it's possible to validate the contact properties only when the addContact button is used, and validate the company properties only when the createCompany button is pressed.
For now i only need to do this serve-side, but if anyone has a solution to do this client-side as well i would appreciate the help.
You could trigger your own validation on the individual objects using
Validator.TryValidateObject(Object, ValidationContext, ICollection)
You can provide conditional validation using the Entity Framework by overriding DbEntityValidationResult in the DbContext. When this validation occurs in the DbContext you can access other entities. When validating a contact you can check the company too. For example:
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
var result = base.ValidateEntity(entityEntry, items);
ValidateContact(result);
return result;
}
private void ValidateContact(DbEntityValidationResult result)
{
var contact= result.Entry.Entity as Contact;
if (contact!= null && contact.ContactId != 0)
{
// Add validation code here, such as:
if(!string.IsNullOrEmpty(contact.Company.Name){
result.ValidationErrors.Add(
new DbValidationError(
"Contact",
"Company name cannot be null or empty when validating contacts.")
);
}
}
}
See Julia Lerman's Programming Entity Framework: DbContext http://www.amazon.com/Programming-Entity-Framework-Julia-Lerman/dp/1449312969 for more details.

Many to one configuration using EF 4.1 code first

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)

EF 4.1 RC: Weird Cascade Delete

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.

Resources