Question

I am not fantastic with EF so maybe it's an easy one.

I Have

    public void DeleteLicense(int licenseId)
    {
        var entityToDelete = context.Licenses.Find(licenseId);
        context.Licenses.Remove(entityToDelete);
    }

I have checked that it finds correct license, and context is a ninject (one per request) DbContext,

But I get a weird error when I call SaveChanges() on the context after running the function above. I get: "The CustomerName field is required."

Now this is weird because CustomerName is in Account (not Licence) they are linked, but still. So here follows some more:

My Account entity

[Required]
public String CustomerName { get; set; }

public virtual ICollection<License> Licenses { get; set; }
...

My License entity

public virtual Account Account { get; set; }
...

My fluent setup

modelBuilder.Entity<Account>().HasMany(x => x.Licenses)
.WithRequired(x => x.Account).WillCascadeOnDelete(false);

I don't understand, because even if there is a failing restraint then why missing CustomerName. I don't touch CustomerName when I delete a license and the CustomerName is set since before.

Update

So here is some more details from the code. The full execution path as far as I can see is

DeleteLicenseAPI below takes the call, the ID is correct, it passes over to a private function. The private function calls the DeleteLicense shown close to the top of the question. The Commit() only calls context.SaveChanges();

public ActionResult DeleteLicenseAPI(int licenseId)
{
        if (DeleteLicense(licenseId))
        {
            return Content("ok");
        }

        return Content("[[[Failed to delete license]]]");

}

private bool DeleteLicense(int licenseId)
{
        //todo: sort out busniess rules for delete, is cascaded?
        _accountRepository.DeleteLicense(licenseId);
        _accountRepository.Commit();

        return true;
}

The _accountRepository looks like this

public class EFAccountRepository : EntityFrameworkRepository<Account>
, IAccountRepository

public EFAccountRepository(EvercateContext context) : base(context)
{       
}

And here is the code in Ninject that sets it all up

kernel.Bind<EvercateContext>()
.To<EvercateContext>()
.InRequestScope()
.WithConstructorArgument("connectionStringOrName", "EvercateConnection");


kernel.Bind<IAccountRepository>().To<EFAccountRepository>();

So even tho I use Unit of Work as far as I can see (and it shouldn't) nothing else is called in this request before running SaveChanges. Is there any way to see what a DbContext will do on SaveChanges, without actually running the method (as it throws DbEntityValidationException)

Était-ce utile?

La solution

I can imagine that this weird exception could occur if you are initializing the Account navigation property in the License constructor like so:

public License
{
    Account = new Account();
}

The flow when you call...

var entityToDelete = context.Licenses.Find(licenseId);
context.Licenses.Remove(entityToDelete);

...is then probably:

  • License entity gets loaded (without navigation property Account) and attached to the context (state Unchanged)
  • The constructor sets the Account navigation property, but it doesn't get attached (state Detached)
  • When you call Remove for the License entity DetectChanges is called internally by EF. It detects that License.Account is refering to a detached entity and attaches it to the context (in state Added). The state of the License is changed to Deleted.
  • When you call SaveChanges the change tracker finds two entities: The License in state Deleted and the Account in state Added.
  • Validation runs and finds that the required property CustomerName for the entity Account that is supposed to be inserted into the database is null (because only the default constructor of Account is called).
  • The validation exception is thrown.

I'm not sure if the details are right but something like that is probably happening.

In any case you should delete the Account = new Account(); from the License constructor and also check if you initialize other reference navigation properties in entity constructors in your codebase as well. (Initializing empty navigation collections is OK.) This is a common source of notoriously strange problems that are difficult to find and understand.

Autres conseils

I tried overriding SaveChanges as recommended. When I did I found a License about to be deleted (as it should) but I also found an Account about to be created.

I changed the DeleteLicense as displayed below.

    public void DeleteLicense(int licenseId)
    {
        var entityToDelete = context.Licenses.Find(licenseId);
        entityToDelete.Account = null;
        context.Licenses.Remove(entityToDelete);
    }

And right away the code works. The License is removed and the account is still there, but no new account is created.

But why, I do not understand why at all. Is it something in the relation i set with fluent api?

In my case this happened because my entity had a [Required] property that was of type int? which made it nullable. While inspecting the model that came back from the db I saw the property had a value but the entity that ended up being saved to the database had that value stripped during SaveChanges for some reason. When I switched the value to the expected int type all worked just fine. :shrug:

I had a similar issue and for me, it looked like I hadn't correctly established the relationship between Parent and Child in their respective classes.

My fix was to add the attributes specified below to the Child class, for the property that represented its Parent's Id

public class Child
{
    [Key, Column(Order = 1)]
    public string Id { get; set; }

    [Key, ForeignKey("Parent"), Column(Order = 2)]  // adding this line fixed things for me
    public string ParentId {get; set;}
}

public class Parent
{
    [Key, Column(Order = 1)]
    public string Id { get; set; }

    ...

    public virtual ICollection<Child> Children{ get; set; }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top