nHibernate ManyToManyToMany with Fluent - c#

I have a structure here I have a ManyToManyToMany relationship.
This is my (truncated) fluent mappings.
public AgencyMap()
{
Id(x => x.AgencyId, "AgencyId");
Map(x => x.AgencyCode, "AgencyCode");
HasManyToMany<Personnel>(x => x.Personnel)
.WithTableName("AgencyPersonnel")
.WithParentKeyColumn("AgencyId")
.WithChildKeyColumn("PersonnelId").Cascade.All().LazyLoad();
}
public PersonnelMap()
{
Id(x => x.PersonnelId, "PersonnelId");
HasManyToMany<Discipline>(x => x.Disciplines)
.WithTableName("AgencyPersonnelDiscipline")
.WithParentKeyColumn("AgencyPersonnelId")
.WithChildKeyColumn("DisciplineId")
.Cascade.SaveUpdate().LazyLoad();
}
public DisciplineMap()
{
SchemaIs("dbo");
Id(x => x.DisciplineId, "DisciplineId");
Map(x => x.DisciplineCode, "DisciplineCode");
Map(x => x.Description, "Description");
}
If I then run code like this.
Agency agency = m_AgencyRepository.Get(10);
var personnel = new Personnel() { ... };
personnel.Disciplines.Add(new Discipline() { ... });
agency.Personnel.Add(personnel);
m_AgencyRepository.Save(agency);
When I run this code I get this error.
could not insert collection: [PPSS.Model.Personnel.Disciplines#22][SQL: SQL not available]
The INSERT statement conflicted with the FOREIGN KEY constraint "AgencyPersonnel_AgencyPersonnelDiscipline_FK1". The conflict occurred in database "PPSS2", table "dbo.AgencyPersonnel", column 'AgencyPersonnelId'.\r\nThe statement has been terminated.
Enitities look like.
public class Agency
{
public virtual int AgencyId {get; set;}
public virtual IList<Personnel> Personnel {get; set;}
}
public class Personnel
{
public virtual int PersonnelId {get; set;}
public virtual IList<Agency> Agencies {get; set;}
public virtual IList<Dependency> Dependencies {get; set;}
}
public class Dependency
{
public virtual int DependencyId {get; set;}
public virtual string Name {get; set;}
}
If I add an inverse to the ManyToMany in personnel then the Personnel is saved but not the disciplines (no exception is raised).
How should this mapping be done?
Edit:
I have got some more info. If I disable the constraint AgencyPersonnel_AgencyPersonnelDiscipline_FK1 then it all is inserted ok. This makes me think it is the order nHibernate is inserting that is the problem. What I would expect it to do in order is.
INSERT INTO Personnel
Get last key (PersonnelId)
INSERT INTO AgencyPersonnelDiscipline with AgencyId and PersonnelId.
This would not violate any constraints.

Since this is a truncated mapping I'm gonna take a shot in the dark :). This kind on of exception usually happens to me when I have 2 entities : X and Y , and I map them from both directions, something like :
X has a property Y
Y has a list of X
And none of them has the Inverse() attribute set. Check if you're mapping something in both directions but not setting one on them Inverse().

Given that the maps you have are correct for the types you state, you haven't mapped back from Personnel to Agency or from Discipline to Personnel.

Related

EF 7 : INSERT statement conflicted with the FOREIGN KEY SAME TABLE

I have an exception when I call SaveChangesAsync.
My architecture is really simple, i have a Category class, wich contains
public class Category {
public Guid ID {get; set;}
public Guid? ParentId {get; set;}
public Category Parent {get; set;}
[...]
}
When I want to insert a new category in database (connected to my ASP MVC application), I set the GUID before doing the insert.
The error occured when my database is empty and I want to insert parent category (so with a null Guid IdParent and null Parent). This is NOT happening if I set a parent value. I can add a record manually by setting parent to Null by Visual studio.
I have the following error :
The INSERT statement conflicted with the FOREIGN KEY SAME TABLE
constraint "FK_Category_Category_ParentId". The conflict occurred in
database "HT_Root", table "dbo.Category", column 'ID'. The statement
has been terminated.
I searched on stack overflow for a simple answer, and not found it.
I try with Fluent API :
modelBuilder.Entity<Category>().HasOne(s => s.Parent).WithMany().HasForeignKey(s => s.ParentId);
But nothing changed. What am I doing wrong ?
It seems to have changed in EF 7 See this github issue
Try
public class Category
{
public Guid ID {get; set;}
public Guid? ParentId {get; set;}
public Category Parent {get; set;}
public ICollection<Categories> Children {get; set;}
}
And
modelBuilder.Entity<Category>()
.HasOne(x => x.Parent)
.WithMany(x => x.Children)
.HasForeignKey(x => x.ParentId)
.Required(false);
You should also always check that (if specified) the ParentId exists in the database. Watch out for adding Guid.Empty (00000000-0000-0000-0000-000000000000) instead of null as this can cause issues.

Entity Framework: How to enable cascade delete on one way related entities

There are Two Entities such as bellow:
public class Business
{
public int Id {get; set;}
public File Logo {get; set;}
public int? LogoId {get; set;}
public File Video {get; set;}
public int? Video {get; set;}
public ICollection<File> Images {get; set;}
}
public class File
{
// some file props, such as Id, Name, ...
}
How can I configure cascade delete for files on business delete?
Please consider that I don't need any navigation from File to Business.
UPDATE:
You may find the modelBuilder configuration as bellow:
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Entity<Entities.Business>()
.HasOptional(b => b.Logo)
.WithOptionalPrincipal()
.WillCascadeOnDelete();
modelBuilder.Entity<Entities.Business>()
.HasOptional(b => b.Video)
.WithOptionalPrincipal()
.WillCascadeOnDelete();
modelBuilder.Entity<Entities.Business>()
.HasMany(b => b.Images)
.WithOptional()
.WillCascadeOnDelete();
and here is the error I've got:
Introducing FOREIGN KEY constraint
'FK_dbo.Files_dbo.Businesses_Business_Id1' on table 'Files' may cause
cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON
UPDATE NO ACTION, or modify other FOREIGN KEY constraints. Could not
create constraint
If you like to use seperate configuration classes you could try something like that:
public class BusinessConfiguration : EntityTypeConfiguration<Business>
{
public BusinessConfiguration()
{
HasMany(x => x.Images).WithOptional().WillCascadeOnDelete();
HasOptional(x => x.Logo).WithOptional().WillCascadeOnDelete();
HasOptional(x => x.Video).WithOptional().WillCascadeOnDelete();
}
}
When you do not pass a lambda within .WithOptional() or .WithRequired() means the other side has no navigation properties.

Nhibernate Lazy load on multiple columns

If I set LazyLoad on multiple properties-columns with NHiberante and access those properties one after the other, would it query the database for each property?
Example:
public class Product
{
public virtual int ID {get; set;}
public virtual string Name {get; set;}
public virtual string FullName {get; set;}
public virtual float Price {get; set;}
}
public class ProductMap : ClassMap<Product>
{
Id(p => p.ID);
Map(p => p.Name).Not.LazyLoad();
Map(p => p.FullName).LazyLoad(); // Redundant - I know...
Map(p => p.Price).LazyLoad(); // Redundant - I know...
}
if I query the DB like this:
var product = session.Load<Prodct>(2);
if (product.FullName == "*" && product.Price = 111)
Will there be 3 queries
The Product entity
The FullName property
The Price property
or when NHibernate query the DB for FullName it will query all the columns of the row?
NHibernate will load all the lazy properties of an entity in a single query (you can try for yourself...)
The main use case for this feature is blobs.
Lazy references, on the other hand, are loaded as needed.
As a side note, session.Load does not query the DB; it just creates a proxy, which will be loaded lazily. Use session.Get.
there will be 2 queries
The Product entity
All LazyLoaded Properties

Fluent NHibernate only generating one extra column for multiple HasMany relationships

I have a thesaurus application built on NHibernate. The data model is very straight-forward:
public class Word
{
public virtual int Id { get; set; }
public virtual string Text { get; set; }
public virtual IList<Word> Synonyms { get; set; }
public virtual IList<Word> Antonyms { get; set; }
}
So each word has a list of synonyms and a list of antonyms. I thought the mapping would be straight-forward as well:
public class WordMapping : ClassMap<Word>
{
public WordMapping()
{
Id(x => x.Id);
Map(x => x.Text);
HasMany(x => x.Synonyms).Cascade.AllDeleteOrphan();
HasMany(x => x.Antonyms).Cascade.AllDeleteOrphan();
}
}
When I look at the table generated in MS SQL Server 2008, I only see one key column for the two HasMany relations:
Id int
Text nvarchar(255)
Word_id int
I think this is causing some weird things to happen when I insert data. When I get the lists after doing some insertions, both Synonyms and Antonyms contain the same Words, but the Words are just seemingly-arbitrary subsets of what should be in the two lists.
Do I have the problem pegged right? If so, how can I fix it?
I was way off base. I assumed FNH was generating a separate table for the HasMany relationships (it wasn't). Proper code found here: Fluent NHibernate Self Referencing Many To Many
Specific to my problem, I created two separate tables:
-- Synonyms table --
WordId int
SynonymId int
-- Antonyms table --
WordId int
AntonymId int
Then in my mapping class:
public class WordMapping : ClassMap<Word>
{
public WordMapping()
{
Id(x => x.Id);
Map(x => x.Text);
HasManyToMany(x => x.Synonyms).ParentKeyColumn("WordId")
.ChildKeyColumn("SynonymId")
.Table("Synonyms");
HasManyToMany(x => x.Antonyms).ParentKeyColumn("WordId")
.ChildKeyColumn("AntonymId")
.Table("Antonyms");
}
}
That fixes all the issues.

Mapping a single field to multiple tables with Fluent NHibernate

Question: is there a way to map a single foreign key to a number of mutually exclusive tables, based on a context?
Background...
In my specific example, I have the following domain graph, representing an insurance claim which can be against a vehicle or property:
public enum InvolvedPartyContext
{
Vehicle = 1,
Property = 2
}
public class Claim
{
public virtual Guid Id { get; set; }
public virtual InvolvedPartyContext InvolvedPartyContext { get; set; }
public virtual Vehicle Vehicle { get; set; } // set if Context = Vehicle
public virtual Property Property { get; set; } // set if Context = Property
}
public class Vehicle { //... }
public class Property { //... }
The SQL looks like this (notice the single foreign key InvolvedPartyId):
CREATE TABLE Claims (
Id uniqueidentifier NOT NULL,
InvolvedPartyContext int NOT NULL,
InvolvedPartyId uniqueidentifier NOT NULL
)
CREATE TABLE Vehicles (
Id uniqueidentifier NOT NULL,
Registration varchar(20) NOT NULL
)
CREATE TABLE Properties (
Id uniqueidentifier NOT NULL,
PostCode varchar(20) NOT NULL
)
The Fluent NHibernate mapping file for Claim:
public ClaimMap()
{
Id(x => x.Id);
Map(x => x.InvolvedPartyContext).CustomTypeIs(typeof(InvolvedPartyContext));
References(x => x.Vehicle, "InvolvedPartyId");
References(x => x.Property, "InvolvedPartyId");
}
This throws an "Invalid index {n} for this SqlParameterCollection with Count {m}" exception, since the same field (InvolvedPartyId) is mapped twice. A simple fix would be to create VehicleId and PropertyId fields, but in the real world there are many more contexts, so this isn't very flexible.
Personally, I wouldn't go with the design you have. Instead I'd create subclasses of your Claim class, VehicleClaim and PropertyClaim respectively.
public class VehicleClaim : Claim
{
public virtual Vehicle Vehicle { get; set; }
}
Then change your mappings to use your InvolvedPartyContext column as a discriminator (the column which NHibernate uses to determine which class the row represents), and create subclass mappings for each subclass.
public class ClaimMap : ClassMap<Claim>
{
public ClaimMap()
{
Id(x => x.Id);
DiscriminateSubClassesOnColumn("InvolvedPartyContext");
}
}
public class VehicleClaimMap : SubclassMap<VehicleClaim>
{
public VehicleClaimMap()
{
DiscriminatorValue(1);
References(x => x.Vehicle);
}
}
If you really do want to run with what you've got, you should look into the any mappings; there isn't a lot of documentation on them, but you use the ReferencesAny method.

Categories