Just for record : I use C#, Fluent NHibernate, and MySQL 5.
I used to use NHibernate for this project, but recently I decided to use Fluent NHibernate instead. And I got problem in mapping the many to many relationship that references to self with additional column.
I have table Item. Item can has many component which is also from table Item. These components can be used by other item as well.
For example, Item A needs two components, which are Item B and Item C. Item B is also used for Item D and Item E, and so on.
I used associative table and I need additional column for their relationship. Because of that, there will be 2 One - Many relations. This is my database structure.
Item
ID
Name
Prerequisite [ Associative table ]
ID
Item_ID [ FK_Item1 ] // item to be made.
Component_ID [ FK_Item2 ] // component for this item.
Weight // additional column.
This is my mapping for Prerequisite Table :
public PrerequisiteMap()
{
Id(x => x.ID).GeneratedBy.Native();
References(x => x.Item).Column("Item_ID");
References(x => x.Component).Column("Component_ID");
Map(x => x.Need);
}
This is my mapping for Item table :
public ItemMap()
{
Id(x => x.ID).GeneratedBy.Native();
HasMany(x => x.PrerequisitesParent).KeyColumn("Item_ID").Cascade.All();
HasMany(x => x.PrerequisitesComponent).KeyColumn("Component_ID").Cascade.All ;
Map(x => x.Name);
}
This is my prerequisite class :
public virtual UInt64 ID { get; set; }
// item.
public virtual UInt64 Item_ID { get; set; }
public virtual Item Item { get; set; }
// Component.
public virtual UInt64 Component_ID { get; set; }
public virtual Item Component { get; set; }
// prerequisite properties.
public virtual float Need { get; set; }
And this is my item class :
// Item properties.
public virtual UInt64 ID { get; protected set; }
public virtual string Name { get; set; }
public virtual IList<Prerequisite> PrerequisitesComponent { get; set; }
public virtual IList<Prerequisite> PrerequisitesParent { get; set; }
The problem I have is, whenever I try to save/update Item with prerequisite, the Component_ID and Item_ID always have the same value. I create new Item X, with components Item Y and Item Z, in prerequisite table, I got these :
ID|Item_ID|Component_ID|Need
1 | X | X | 10
2 | X | X | 20
instead of, the expected result
1 | X | Y | 10
2 | X | Z | 20
This is my piece of code when saving :
using (var session = SessionFactoryProvider.OpenSession())
{
using (var trans = session.BeginTransaction())
{
var item = session.Get<Item>((UInt64)1); // Item to be updated.
var item2 = session.Get<Item>((UInt64)2); // Component 1
var item3 = session.Get<Item>((UInt64)3); // Component 2
item.PrerequisitesComponent.Add(new Prerequisite() { Item = item, Component = item2, Need = 100f}); // adding new prerequisite from component 1 (item2)
item.PrerequisitesComponent.Add(new Prerequisite() { Item = item, Component = item3, Need = 100f }); // adding new prerequisite from component 2 (item3)
session.SaveOrUpdate(item);
try
{
trans.Commit();
}
catch(GenericADOException ex)
{
MessageBox.Show(ex.InnerException.ToString());
}
}
}
What could be the problem here? Is it because I reference the same table? or maybe I mapped it wrongly?
I read other similar questions many to many from product to product and many to many self referencing but it seems like they didn't use additional column in their associative table?
Any help is very appreciated, Thank You.
Silly Me.. I used the wrong reference for item.
I changed item.PrerequisiteComponent to item.PrerequisiteParent and it all works well.
Related
Is it possible to create a mapping which would fill my navigation property automatically, based on table's PK? That means I don't want to have a FK on any table. I know it is possible when there is Optional -> Required mapping. AFAIK no constraint is created on SQL Server, the relationship is "simulated" by EF6 in runtime.
|====================| |====================|
| Car | | Driver |
|====================| |====================|
| Id | 0..1 ----- 0..1 | Id |
|--------------------| |--------------------|
So that code _dbContext.Cars.Include(x => x.Driver).First(x => x.Id == 1); would return a car with driver if exists, or null driver if doesn't exist.
class Car
{
public int Id { get; set; }
public virtual Driver Driver { get; set; }
}
class Driver
{
public int Id { get; set; }
public virtual Car Car { get; set; }
}
The following mappings wants to create a DriverId in my Car:
class CarMapping : EntityTypeConfiguration<Car>
{
public CarMapping()
{
HasKey(x => x.Id);
}
}
class DriverMapping : EntityTypeConfiguration<Driver>
{
public CarMapping()
{
HasKey(x => x.Id);
HasOptional(x => x.Car)
.WithOptionalPrincipal(x => x.Driver);
}
}
EDIT
I checked with mentioned earlier required mapping:
class CarMapping : EntityTypeConfiguration<Car>
{
public CarMapping()
{
HasKey(x => x.Id);
}
}
class DriverMapping : EntityTypeConfiguration<Driver>
{
public CarMapping()
{
HasKey(x => x.Id);
HasOptional(x => x.Car)
.WithRequired(x => x.Driver);
}
}
And it works. EF6 does not perform any validation on Inserts nor Deletes... Is this a bug? Well at least it works for my case.
As far as I understand you want a one-to-one relationship between a Car and a Driver i.e. each Car can have zero or one driver. If so you need to define the Driver's primary key as a foreign key to the Car's entity. Have you tried this
class Driver
{
[ForeignKey("Car")]
public int Id { get; set; }
public virtual Car Car { get; set; }
}
This way only a Car record will have only one Driver record referencing it at the same time.
I'm struggling a bit with an NHibernate mapping within a console app.
Here is the summary:
It's a one to many relationship. PARENT_TABLE (one) to CHILD_TABLE (many)
PARENT_TABLE has a composite key consisting of two keys: Vendor and
Invoice
Id(x => x.Invoice).Column("INVOICE");
References(x => x.Distribution).Column("INVOICE");
CHILD_TABLE has a composite key consisting of three: Vendor,
Invoice, and Num
Id(x => x.Invoice).Column("INVOICE");
HasOne(x => x.Invoice).ForeignKey("INVOICE").Cascade.Refresh();
When I run through some data that has the following example:
Vendor Invoice Num
10 | 44 | 1
11 | 44 | 1
11 | 44 | 2
I end up getting an error because of the duplicate ID in Invoice (Makes total sense)
Then, I tried using a composite ID using 3 keys for Distribution and 2 keys for Invoice. This, as I'm sure many of you already know, also ended up with an error since there are two records that use 11 and 44 for Vendor and Invoice, respectively.
My question: Is there a way to declare the relationship using a composite ID (INVOICE and VENDOR) and still have the child collection respect/enforce the uniqueness of the 3 key composite (INVOICE, VENDOR, and NUM)?
I've tried several permutations of composite keys and haven't been able to figure it out. Maybe it's just not designed for this. Any help would be much appreciated!
Given you have a composite id definition on your entities, you have to map it using the CompositeId method. In your child entity, you also have to define the references for key columns of your parent entity, because it is a composite id (and that's the reason where I personally do not enjoy working with composite IDs).
Considering your model:
public class ParentEntity
{
public virtual int Vendor { get; set; }
public virtual int Invoice { get; set; }
// other properties
}
public class ChildEntity
{
public virtual int Vendor { get; set; }
public virtual int Invoice { get; set; }
public virtual int Num { get; set; }
public virtual ParentEntity ParentEntity { get; set; }
}
Your mappings could be like this:
public class ParentEntityMap : ClassMap<ParentEntity>
{
public ParentEntityMap()
{
CompositeId()
.KeyProperty(x => x.Vendor, "VENDOR")
.KeyProperty(x => x.Invoice, "INVOICE");
// other mappings ...
}
}
public class ChildEntityMap : ClassMap<ChildEntity>
{
public ChildEntity()
{
CompositeId()
.KeyProperty(x => x.Vendor, "VENDOR")
.KeyProperty(x => x.Invoice, "INVOICE")
.KeyProperty(x => x.Num, "NUM");
References(x => x.ParentEntity)
.Columns("VENDOR", "INVOICE")
.ReadOnly(); // if you are mapping the same columns of compositeId here, define it as readOnly to avoid inserting data two times on the same columns.
}
}
I did not try this code, it is just a suggestion to check what could work for you.
I have started using this extension, and just want to say its excellent, thank you!
Now i have an issue, where an object can be moved from 1 collection, into another collection, and when i do this, i get an exception
InvalidOperationException: Multiplicity constraint violated
I am guessing this is because the object isnt being found in the original collection, and this extension is adding the object to the new collection, even though i want it too be moved, then upon saving, EF throws the exception, because i have 2 objects with the same key against my context.
But how can i get this to work?
So if i have the following object structure
MyRoot
| Collection
| MyChild
| Collection
| MyObject (1)
| MyChild
| Collection
| MyObject (2)
How can i move MyObject (1) into the same collection as MyObject (2)??
These are all basic objects, and here is some simple code
public class MyRoot
{
public int Id { get; set; }
public ICollection<MyChild> MyChildren { get; set; }
}
public class MyChild
{
public int Id { get; set; }
public int RootId { get; set; }
public MyRoot Root { get; set; }
public ICollection<MyObject> MyObjects { get; set; }
}
public class MyObject
{
public int Id { get; set; }
public string Name { get; set; }
public int ChildId { get; set; }
public MyChild Child { get; set; }
}
Each of these objects have a DTO, for the sake of this example, lets just say the objects are exactly the same, with extension DTO on the end (this is not the case in real application)
In my application, i then have an automapper profile, like so
internal class MyProfile: Profile
{
public MyProfile()
{
this.CreateMap<MyRoot, MyRootDTO>()
.ReverseMap();
this.CreateMap<MyChild, MyChildDTO>()
.ReverseMap()
.EqualityComparison((s, d) => s.Id == d.Id);
this.CreateMap<MyObject, MyObjectDTO>()
.ReverseMap()
.EqualityComparison((s, d) => s.Id == d.Id);
}
}
On my web api controller method, i have this, which is very simple
public async Task<IActionResult> UpdateAsync([FromBody] MyRootDTO model)
{
// get the object and all children, using EF6
var entity = await _service.GetAsync(model.Id);
// map
_mapper.Map(model, entity);
// pass object now updated with DTO changes to save
await _service.UpdateAsync(entity);
// return
return new OkObjectResult(_mapper.Map<MyRootDTO>(entity));
}
If someone could please help, that would be great!
I don't think your problem has anything to do with AutoMapper here, it's an Entity Framework problem. If you remove something from a child collection in EF, it doesn't automatically get deleted unless you either call a .Delete on it, or the key for the object is a composite key including the parent.
I would suggest making a composite key, such as the following:
public class MyObject
{
[Column(Order = 1)]
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
[Column(Order = 0)]
[Key]
public int ChildId { get; set; }
public MyChild Child { get; set; }
}
The [DatabaseGenerated] option keeps the Id column as an Identity - EF's default with a composite key is for no automatic identity.
You can do the same thing on your MyChild entity.
To get this to work, i didnt change EF keys, but implemented a method in my AutoMapper profile. I looped through the object to see if the child was in a different list, and if so, moved the object into that new list. This way automapper will be able to match the object based on ID still.
I added the below code into the .BeforeMap method
Not that my base level object is called Root in this example, so parameter s is type RootModel (from my web api) and parameter d is type Root (from EF). Both RootModel and Root have a collection called Sections
.BeforeMap((s, d) =>
{
// we are going to check if any child has been moved from 1 parent to another, and
// if so, move the child before the mapping takes place, this way AutoMapper.Collections will not
// mark the object as orphaned in the first place!
foreach (var srcParent in s.Sections)
{
// only loop through old measures, so ID will not be zero
foreach (var srcChild in srcParent.Children.Where(e => e.Id != 0))
{
// check if the srcChild is in the same dest parent?
var destChild = d.Sections.SelectMany(e => e.Children).Where(e => e.Id == srcChild.Id).FirstOrDefault();
// make sure destination measure exists
if (destChild != null)
{
// does the destination child section id match the source section id? If not, child has been moved
if (destChild.ParentId != srcParent.Id)
{
// now we need to move the child into the new parent, so lets find the destination
// parent that the child should be moved into
var oldParent = destChild.Parent;
var newParent = d.Sections.Where(e => e.Id == srcParent.Id).FirstOrDefault();
// remove child from children collection on oldSection and add to newSection
oldParent.Children.Remove(destChild);
// if newParent is NULL, it is because this is a NEW section, so we need to add this new section
// NOTE: Root is my based level object, so your will be different
if (newParent == null)
{
newParent = new Parent();
d.Sections.Add(newParent);
newParent.Root = d;
newParent.RootId = d.Id;
}
else
{
// change references on the child
destChild.Parent = newParent;
destChild.ParentId = newParent.Id;
}
newParent.Children.Add(destChild);
}
}
}
}
})
I have Item table which have manyTomany to itself:
public virtual ICollection<Item> Related { get; set; }
modelBuilder.Entity<Item>().HasMany(x => x.Related ).WithMany();
Data:
Item_Id Item_Id1
8 2
8 3
8 4
How can I allow duplicate , meaning allow another 8-2 for example, when I try to insert another "duplicate" data , it shows only 1.
I think that the origin of the problem is to misunderstand what an entity is. An entity is unique, and has a unique Id, so for example, a company could have a collection of employed people, that are unique, so you cannot have two instances of the same person in the same collection. If you have a shopping cart, you have to think to the items in the collection as 'order lines' and not as the items itself. Any order line has its own Id and a reference to an entity, that is the object you are purchasing.
I didn't check this specifically, so you may need to work out the details -
but (I think) the only way to do it is to manually define the index table (e.g. Item2Item).
Check this post I made for additional info
EF code-first many-to-many with additional data
That is on how to create a custom table for many-to-many - with adding
additional fields.
Now that you have a custom table, you need to change it a bit - you need to remove the composite keys from the .HasKey. Instead use your own pk, e.g. identity as for any other table.
i.e. you can define a unique PK, identity like for any other table - and have that as your key. That should let you have duplicate keys in your fk columns).
Check this post as it seems logically close (though not the same)
Many to many (join table) relationship with the same entity with codefirst or fluent API?
Well this gave me the final solution--> EF Code First, how can I achieve two foreign keys from one table to other table? , NSGaga thanks for the direction
public class Item : IValidatableObject
{
[Key]
public int ID { get; set; }
public virtual ICollection<ItemRelation> Related{ get; set; }
public string Name { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (....)
{
yield return new ValidationResult("Name not valid", new[] { "Name" });
}
}
}
public class ItemRelation
{
[Key]
public int ID { get; set; }
public int ItemAID { get; set; }
public virtual Item ItemA { get; set; }
public int ItemBID { get; set; }
public virtual Item ItemB { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<ItemRelation>().HasRequired(c => c.ItemA)
.WithMany(m => m.Related)
.HasForeignKey(c => c.ItemAID)
.WillCascadeOnDelete();
modelBuilder.Entity<ItemRelation>().HasRequired(c => c.ItemB)
.WithMany()
.HasForeignKey(c => c.ItemBID)
.WillCascadeOnDelete(false);
}
public DbSet<Item> Items { get; set; }
public DbSet<ItemRelation> ItemsRelations { get; set; }
Data:
Id ItemA_Id ItemB_Id
1 8 2
2 8 3
3 8 5
4 8 5
5 8 5
6 1 6
7 5 4
8 2 9
I have a situation where one of my table is self-mapped to itself. The primary key of one row (Parent) can be used as a foreign key to other row (Child) and this foreign key column contains null for such rows that have no parent. Something like this:
table: Settings_LocationType
++++++++++++++++++++++++++++++++++++++++++++++++
LocationID | Name | ParentLocationId
++++++++++++++++++++++++++++++++++++++++++++++++
1 Parent 1 null
2 Child 1 1
3 Child 2 1
4 Parent 2 null
Model: LocationType
public class LocationType
{
public virtual long LocationTypeId { get; set; }
public virtual string Name { get; set; }
public virtual LocationType ParentLocationType { get; set; }
public virtual IList<LocationType> LocationTypes { get; set; }
public LocationType()
{
LocationTypes = new List<LocationType>();
}
}
Mapping: LocationTypeMap
public class LocationTypeMap : ClassMap<LocationType>
{
public LocationTypeMap()
{
Table("Setting_LocationType");
Id(x => x.LocationTypeId).Column("LocationId").GeneratedBy.Sequence("location_type_seq");
Map(x => x.ShortName, "Name").Length(15).Not.Nullable();
References<LocationType>(x => x.ParentLocationType).LazyLoad().Nullable();
HasMany<LocationType>(x => x.LocationTypes).AsBag().KeyColumn("ParentLocationId").KeyNullable().LazyLoad().Inverse().Cascade.SaveUpdate();
}
}
Now I am having a problem in retrieving those rows which contain NULL (or say aren't child) in PatentLocationType field. I tried passing null like this repo.Get("ParentLocationType.LocationTypeId", null); but it didnt work but threw object reference is not set to an instance error.
Have you tried:
repo.Get ("ParentLocationType", null)
OK, I solved it using Expression.IsNull instead of Expression.Eq when querying for such LocationType