Strange Foreign Key constraint in fluent mapped SQLite - c#

I have a model written using FluentNhibernate and I am trying to create some test data in an in-memory SQLite database.
var fConfig =
Fluently.Configure()
.Database(config)
.Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetAssembly(exampleClass))
.Conventions.Add(AutoImport.Never())
.Conventions.Add(new SQLiteGeometryTypeConvention())
.Conventions.Add(FluentNHibernate.Conventions.Helpers.DefaultLazy.Never()))
.ExposeConfiguration(cfg => configuration = cfg);
The problem I am getting is that one of the tables is created like this in the generated sql something like this :
create table myTable (
myID integer primary key autoincrement,
...{skip normal columns}
ForeignTable1ID INT,
ParentID BIGINT,
constraint FK1A2E045AFEC6908F foreign key (ForeignTable1ID) references ForeignTable1,
constraint FK1A2E045ABB4EBD1F foreign key (ParentID) references Parent,
constraint FKE21911CF6853D06E foreign key (myID) references Parent
)
I don't want that third constraint but don't know what's causing it!
The effect being that I can only create records in myTable which have valid foreign keys BUT ALSO have a myID value which exists in the Parent table. This is unnecessary, and I can't see what's causing it.
The mapping file looks like this:
Table("dbo.PalslagInventering");
Id(x => x.myId).Column("myID");
References(x => x.ForeignTable1).Column("ForeignTable1ID");
References(x => x.Parent).Column("ParentID");
Map(x => x.{other columns here});
HasMany(x => x.Child).KeyColumn("myID");
HasOne(x => x.Child2).ForeignKey("MyID");
HasMany(x => x.Child2).KeyColumn("MyID").ForeignKeyConstraintName("Child2");
HasMany(x => x.Child3).KeyColumn("MyID").ForeignKeyConstraintName("Child3");
The parent table (being referenced) has a simple
public virtual IList<MyTable> myTableRecords { get; private set; }
Type of code mapped as:
HasMany(x => x.myTableRecords)
.KeyColumn("myID")
.Inverse();
What is causing the "foreign key" reference back to it's own myId?

The error seems to be:
HasMany(x => x.myTableRecords)
.KeyColumn("myID")
.Inverse();
Where it's the Child table (i.e. "MyTable") which has the ID "myID" and the KeyColumn should point to the Parent table (i.e. the foreign key in "MyTable").
So:
HasMany(x => x.myTableRecords)
.KeyColumn("ParentID")
.Inverse();
Seems to have fixed the problem. That the error was in the parent class's mapping file meant I missed it at first.

Related

Adding a new item to a collection using Fluent NHibernate doesn't INSERT the new row in the DB

I have a model called ShoppingLists and each ShoppingLists has a collection of ShoppingListItems called Items. What I would like to be able to do is add a new item to my list as such:
dbList.Items.Add(new ShoppingListItems(Guid.NewGuid(), identity.UserId, source.Raw));
I would expect the ShoppingListItems to automatically be linked to its parent ShoppingLists class, and for NHibernate to create the appropriate SQL INSERT statement when the transaction is committed. However, instead I get the exception:
NHibernate.StaleStateException was unhandled
HResult=-2146232832
Message=Unexpected row count: 0; expected: 1
Source=NHibernate
What I have to do instead is create the object, save it, then add it to the collection:
var newItem = new ShoppingListItems(Guid.NewGuid(), identity.UserId, source.Raw);
newItem.ShoppingList = dbList;
session.Save(newItem);
dbList.Items.Add(newItem);
I'd like to eliminate the need to do this. My mappings for ShoppingLists is as such:
Id(x => x.ShoppingListId);
Map(x => x.UserId).Not.Nullable();
Map(x => x.Title).Not.Nullable();
HasMany(x => x.Items)
.KeyColumn("ShoppingListId")
.Cascade.Delete(); // If Shopping List is deleted, delete all the Items that reference this list
And my mappings for ShoppingListItems is:
Id(x => x.ItemId);
Map(x => x.Raw).Length(50);
Map(x => x.Qty);
Map(x => x.Unit);
Map(x => x.UserId).Not.Nullable();
Map(x => x.CrossedOut).Not.Nullable();
References(x => x.Recipe).Column("RecipeId");
References(x => x.Ingredient).Column("IngredientId");
References(x => x.ShoppingList).Column("ShoppingListId");
I've tried playing around with Cascade.All() on each, to no avoid. Any ideas?
The issue here is related to the ShoppingListItem Guid ID handling (see the code snippets of the item creation in the question).
The "working" scenario, when the ShoppingListItem is created and persisted, does the following step - generates the Guid ID and passes it into the constructor
var newItem = new new ShoppingListItems(Guid.NewGuid()...)
Next, NHiberante is asked to do session.Save(newItem), so the INSERT statement is issued...
But what will happen in the second case, when the session.Save(newItem) is not called, only Add() the item into the collection of the parent ShoppingLists?
As mentioned here: 5.1.4.7. Assigned Identifiers (an extract)
...entities that use assigned generator cannot be saved via the ISession's SaveOrUpdate() method. Instead you have to explicitly specify to NHibernate if the object should be saved or updated...
What is NHibernate doing in this case (when the session.Flush() is called) is guessing: should be INSERT or UPDATE issued for each of the collection items?
When it comes to the newItem, it compares its Guid ID with the UnsavedValue(), which is in this case Guid.Empty.
This comparison will result in a decision: Issue UPDATE statement, the Guid ID does not represent new object.
The UPDATE is executed. DB responds: Update done, no errors, but also no rows updated*. NHibernate throws:
Unexpected row count: 0; expected: 1
Solution
Leave the GUID generation on the NHibernate. E.g.:
Id(x => x.ItemId)
.GeneratedBy.GuidComb()
.UnsavedValue(Guid.Empty);
And also, rather explicitly set the newItem reference to its parent, an optimize the cascade with Inverse() setting
// both assignments
newItem.ShoppingList = dbList;
dbList.Items.Add(newItem);
The list mapped with inverse:
// improved write operations handling
HasMany(x => x.Items)
.KeyColumn("ShoppingListId")
.Inverse()
...
please recheck the mappings for ShoppingListItems
You have to do a cascade all and inverse should be true
you have a bidirectional association between shoppinglist and item.
You may specify a bidirectional many-to-many association simply by mapping two many-to-many associations to the same database table and declaring one end as inverse.

Fluent NHibernate PersistentSpecification Test fails

I currently have these Maps
public class CountryMap : ClassMap<Country>
{
public CountryMap()
{
Table("tblCountry");
Id(x => x.Id, "intCountryId");
Map(x => x.Name, "strCountryName");
HasMany(x => x.FlagImages)
.Table("tblImage")
.KeyColumn("intRelId")
.Where("intObjId=29")
.Not.Cascade.All();
}
}
public class ImageMap : ClassMap<Image>
{
public ImageMap()
{
Table("tblImage");
Id(x => x.Id, "intImgId");
Map(x => x.ObjId, "intObjId");
Map(x => x.RelId, "intRelId");
Map(x => x.ImageName, "strImage");
}
}
and the following persistent specification test:
PersistenceSpecification<Image>(session)()
.CheckProperty(r => r.Id, 1)
.CheckProperty(r => r.ImageName, "ss")
.CheckProperty(r => r.ObjId, (int)ObjectType.Country)
.CheckProperty(r => r.RelId, 102)
.VerifyTheMappings();
Somewhat the test of Image failed.
The failure message is
Assert.AreEqual failed. Expected:<(null)>. Actual:<NHibernate.Exceptions.GenericADOException: could not insert: [HansaCrew.Models.Images.Image][SQL: INSERT INTO tblImage (intObjId, intRelId, strImage) VALUES (?, ?, ?); select last_insert_rowid()] ---> System.Data.SQLite.SQLiteException: constraint failed
foreign key constraint failed
I've checked it for 2 days and haven't find the reason. Any help?
Okay, I got it.
The problem of this piece of code lies here
HasMany(x => x.FlagImages)
.Table("tblImage")
.KeyColumn("intRelId")
.Where("intObjId=29")
.Not.Cascade.All();
NHibernate automatically creates a foreign key constraint with tblImage, which has a lot of intRelIds which is not in the column of intCountryId in the table tblCountry. That's why the test fails saying foreign key constraint failure.
The way to solve this problem is to create a view in the database and then map the view into the Image model

Entity Framework Code First One to One relationship on composite key

Basically what I am trying to achieve is I have three tables, one parent will always have an entry in it and only one of the other two tables will be populated. The complexity that I am dealing with is that the primary key for the tables is the combination of two fields
ParentTable
-----------
UniqueID
OwnerID
[Some more fields]
ChildTable1
-----------
UniqueID
OwnerID
[Some more fields]
ChildTable2
-----------
UniqueID
OwnerID
[Some more fields]
I was wondering if anyone has any suggestions on how best to do this through EF Code First preferably using the Fluent API.
You just need to define that the primary keys are composite...
modelBuilder.Entity<Parent>().HasKey(p => new { p.UniqueID, p.OwnerID });
modelBuilder.Entity<Child1>().HasKey(c => new { c.UniqueID, c.OwnerID });
modelBuilder.Entity<Child2>().HasKey(c => new { c.UniqueID, c.OwnerID });
...and then define the two one-to-one relationships:
modelBuilder.Entity<Parent>()
.HasOptional(p => p.Child1)
.WithRequired(); // or .WithRequired(c => c.Parent)
modelBuilder.Entity<Parent>()
.HasOptional(p => p.Child2)
.WithRequired(); // or .WithRequired(c => c.Parent)
You cannot define a constraint though (except probably by defining a trigger in the database) that would ensure that a given parent may only refer to one of the two children, but not to both. You must handle this restriction in your application's business logic.

Foreign Key to a table with 2 columns primary key (CompositeId)

I've defined the primary key as following:
CompositeId()
.KeyProperty(x => x.Id)
.KeyProperty(x => x.Type);
I've tried the following:
References(x => x.EntityWith2ColsPK);
And failed with:
Foreign key (Fk_MyEntity_EntityWith2ColsPK:MyEntities [Fk_EntityWith2ColsPK])) must have same number of columns as the referenced primary key (EntityWith2ColsPKs [Id, Type])
How can I reference EntityWith2ColsPK from another entity?
Update:
I've tried the following (according to AlfeG's comment):
HasMany<EntityWith2ColsPK>(x => x.EntityWith2ColsPK).KeyColumns.Add("Id", "Type").Cascade.All();
Which failed with:
Custom type does not implement UserCollectionType: EntityWith2ColsPK
But anyway I don't want a 1 to many relation, I want a 1 to 1 relation. Still, I can't make either of them work.
Also, I've tried:
HasOne<EntityWith2ColsPK>(x => x.EntityWith2ColsPK).PropertyRef(x => x.Id).PropertyRef(x => x.Type);
Which fails with:
NHibernate.MappingException : property not found: Type on entity EntityWith2ColsPK
What can I do for this to really work?
I managed to achieve something in the db.. but yet, for some reason I suspect it maps the property "Type" twice, because I want it to be both part of the Primary Key, and part of the Foreign Key..
This is what I did:
References(x => x.EntityWith2ColsPK).Columns("EntityWith2ColsPKId", "Type").Formula("Id = :EntityWith2ColsPKId AND Type = :Type");
But I received the following exception:
System.IndexOutOfRangeException : Invalid index 8 for this SqlParameterCollection with Count=8.
Because the mapping of this entity is same as EntityWith2ColsPK:
CompositeId()
.KeyProperty(x => x.Id)
.KeyProperty(x => ((ILocalizedEntity) x).Language);
HELP!
You can use something like this since you aren't using cascade anyway on your Reference
References(x => x.EntityWith2ColsPK)
.Columns(new string[] { "ID", "TYPE" })
.Not.Update()
.Not.Insert();

NHibernate fluent update problem (one to many relation)

I have a object OtherFaclilityEntity which contains a IList of OtherFcs objects, and the mapping is as follows:
public OtherFacilityMap()
{
Schema("SOME");
Table("OTHER_FACILITY");
Id(x => x.Id, "OTHER_FACILITY_S").GeneratedBy.TriggerIdentity();
Map(x => x.RowCreator, "ROW_CREATOR");
Map(x => x.RowCreateDate, "ROW_CREATE_DATE");
Map(x => x.Description, "DESCRIPTION");
Map(x => x.ExistenceKdNm, "R_EXISTENCE_KD_NM");
References(x => x.FacilityClassItem, "FACILITY_CLASS_S").LazyLoad(Laziness.False).Fetch.Join().Not.Insert().Not.Update();
HasMany(x => x.FacilityCmList).KeyColumn("WHOLE_S").Fetch.Subselect().Not.LazyLoad();
}
When i try to do a SaveOrUpdate on the OtherFacility entity, it also updates all the entities in the FacilityCmList, which is fine, but in the last sql that is run tries to remove all relations between the parent and the child objects:
NHibernate.SQL: 2011-07-19 10:29:33,111 [361] DEBUG NHibernate.SQL [(null)] - UPDATE SOME.FACILITY_CMS SET WHOLE_S = null WHERE WHOLE_S = :p0;:p0 = '26021842'
I assume it has something to do with my mapping, any ideas?
After reading NHibernate sets Foreign Key in secondary update rather than on initial insert violating Not-Null Constrain on key column i found that i needed to add Inverse to the HasMany relation.
HasMany(x => x.FacilityCmList).KeyColumn("WHOLE_S").Fetch.Subselect().Inverse().Not.LazyLoad();

Categories

Resources