简体   繁体   中英

Fluent NHibernate Mapping / Parent/Child delete fail using composite key

I'm struggling to understand why when I remove a child Settings object from MyUser.Settings and SAVE MyUser I get SQL errors like below:

Cannot insert the value NULL into column 'MyUserId', table '###.Settings'; column does not allow nulls. UPDATE fails.
The statement has been terminated.

What I would expect to happen is that removing the item from the collection, then saving MyUser causes NHibernate to issue a DELETE command for the given child. However, what it does is UPDATE the relevant row for the Settings object, setting MyUserId to NULL - which isn't allowed as I'm using a Composite Key.

I've tried so many combinations of Inverse() and the various Cascade options but nothing seems to work. I should point out that Adding to the collection works perfectly when I save MyUser.

I'm totally baffled!

Below is pseudo code to try and explain my entities and mappings.

public class SettingType
{
    public virtual int SettingTypeId { get; set; }
    public virtual string Name { get; set; }
    public virtual bool Active { get; set; }
}

public class Setting
{
    public virtual MyUser MyUser { get; set; }
    public virtual SettingType SettingType { get; set; }
    public virtual DateTime Created { get; set; }
}

public class MyUser
{
    public virtual int MyUserId { get; set; }
    public virtual IList<Setting> Settings { get; set; }
    public virtual string Email { get; set; }

    public void AddSetting(SettingType settingType, DateTime now)
    {
        var existing = _settings.SingleOrDefault(s => s.SettingType.SettingTypeId == settingType.SettingTypeId);

        if (existing != null)
        {
            existing.Updated = now;
        }
        else
        {
            var setting = new Setting
            {
                MyUser = this,
                SettingType = settingType,
                Created = now,
            };

            _settings.Add(setting);
        }
    }

    public void RemoveSetting(SettingType settingType)
    {
        var existingPref = _settings.SingleOrDefault(s => s.SettingType.SettingTypeId == settingType.SettingTypeId);

        if (existingPref != null)
        {
            _settings.Remove(existingPref);
        }
    }

    private readonly IList<Setting> _settings = new List<Setting>();
}

And my mappings:

public class SettingTypeMap : IAutoMappingOverride<SettingType>
{
    public void Override(AutoMapping<SettingType> mapping)
    {
        mapping.Table("SettingTypes");
        mapping.Id(m => m.SettingTypeId).GeneratedBy.Identity();
        mapping.Map(m => m.Name).Not.Nullable().Length(100);
        mapping.Map(m => m.Active).Not.Nullable().Default("0");
    }
}

public class SettingMap : IAutoMappingOverride<Setting>
{
    public void Override(AutoMapping<Setting> mapping)
    {
        mapping.Table("Settings");
        mapping.CompositeId()
            .KeyReference(m => m.MyUser)
            .KeyReference(m => m.SettingType);
        mapping.Map(m => m.Created).Not.Nullable().Default("CURRENT_TIMESTAMP");
        mapping.Map(m => m.Updated).Nullable();
    }
}

public class MyUserMappingOverride : IAutoMappingOverride<MyUser>
{
    public void Override(AutoMapping<MyUser> mapping)
    {
        mapping.Table("MyUsers");
        mapping.Id(m => m.MyUserId).GeneratedBy.Identity();
        mapping.Map(m => m.Email).Not.Nullable().Length(200);
        mapping.HasMany(m => m.Settings).KeyColumn("MyUserId").Cascade.DeleteOrphan()
            .Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);
    }
}

All using:

FluentNHibernate v1.3.0.733

NHibernate v3.3.1.4000

UPDATE: After a few suggestions I've tried to change the mapping for MyUser entity.

First to this:

 mapping.HasMany(m => m.Settings)
            .KeyColumn("MyUserId")
            .Inverse()
            .Cascade.DeleteOrphan()
            .Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);

This gives the error: Given key was not present in the dictionary

So tried to add second key column:

 mapping.HasMany(m => m.Settings)
            .KeyColumn("MyUserId")
            .KeyColumn("SettingTypeId")
            .Inverse()
            .Cascade.DeleteOrphan()
            .Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);

But this then causes odd behaviour when loading the Settings collection from the DB for a given MyUserId. Looking at the nh profiler I see a second SELECT ... FROM Settings but setting the SettingTypeId same as value for MyUserId.

Still totally baffled. Has cost me too much time so going to revert to adding a primary key id field to the Settings entity. Maybe you just can't do what I'm trying using NHibernate. In pure SQL this is simple.

You should use the Inverse mapping

mapping.HasMany(m => m.Settings)
  .KeyColumn("MyUserId")
  .Inverse()
  .Cascade.DeleteOrphan()
  .Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);

This will allow NHibernate to ask the setting itself to be deleted. Otherwise, NHibernate firstly tries to delete the relation, and would try to delete the entity.

See: 6.4. One-To-Many Associations

Very Important Note: If the column of a association is declared NOT NULL, NHibernate may cause constraint violations when it creates or updates the association. To prevent this problem, you must use a bidirectional association with the many valued end (the set or bag) marked as inverse="true". See the discussion of bidirectional associations later in this chapter.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM