I have the following database structure, which I cannot change:
CREATE TABLE Users (
ID INT NOT NULL IDENTITY(1,1),
PRIMARY KEY(ID)
)
CREATE TABLE UserAvatars (
UserID INT NOT NULL,
Width INT NOT NULL,
Height INT NOT NULL,
PRIMARY KEY(UserID),
FOREIGN KEY(UserID) REFERENCES Users(ID),
)
Basically, a User can have Zero-or-one avatars (I removed columns to simplify the example).
class User
{
public int ID { get; protected set; }
public UserAvatar Avatar { get; set; }
}
class UserAvatar
{
public User User { get; set; }
public int Width { get; set; }
public int Height { get; set; }
}
I am using the following mapping:
class UserMapping : ClassMapping<User>
{
public UserMapping()
{
Id(x => x.ID, m => {
m.Generator(Generators.Identity);
});
OneToOne(x => x.Avatar);
}
}
class UserAvatarMapping : ClassMapping<UserAvatar>
{
public UserAvatar()
{
Id(x => x.User, m =>
{
m.Generator(Generators.Foreign<User>(u => u.ID));
});
Property(x => x.Width);
Property(x => x.Height);
}
}
However, when trying run my app, I get an NHibernate.MappingException:
NHibernate.MappingException : Could not compile the mapping document:
mapping_by_code----> NHibernate.MappingException : Could not determine
type for: MyApp.Models.UserAvatar, MyApp, for columns:
NHibernate.Mapping.Column(User)
I can't make any sense of this cryptic error.
How do I accomplish the mapping where a User could have zero or one Avatars?
Taking from Radim Köhler's guidence, the final classes / mapping that solved the problem:
class User
{
public int ID { get; protected set; }
public UserAvatar Avatar { get; set; }
}
class UserAvatar
{
public int UserID { get; protected set; }
public User User { get; set; }
public int Width { get; set; }
public int Height { get; set; }
}
With the following mapping:
class UserMapping : ClassMapping<User>
{
public UserMapping()
{
Id(x => x.ID, m => {
m.Generator(Generators.Identity);
});
OneToOne(x => x.Avatar, m =>
{
m.Cascade(Cascade.All);
m.Constrained(false);
});
}
}
class UserAvatarMapping : ClassMapping<UserAvatar>
{
public UserAvatar()
{
Id(x => x.UserID, m =>
{
m.Generator(Generators.Foreign<UserAvatar>(u => u.User));
});
OneToOne(x => x.User, m =>
{
m.Constrained(true);
});
Property(x => x.Width);
Property(x => x.Height);
}
}
The ID is important almost for every entity mapped by NHibernate (ORM). So, we should extend the Avatar class:
class UserAvatar
{
// every(stuff) should have its ID
public int ID { get; protected set; }
public User User { get; set; }
...
The ID value will be managed by one-to-one relationship. So, now let's adjust the mapping.
public UserAvatar()
{
Id(x => x.ID);
HasOne(x => x.User)
.Constrained()
.ForeignKey();
...
public UserMapping()
{
Id(x => x.ID);
HasOne(s => s.Avatar).Cascade.All();
Related and really interesting reading:
How to do a fluent nhibernate one to one mapping?
Fluent NHibernate & HasOne(): how to implement a one-to-one relationship
Related
I have the following model:
public partial class Device
{
public string Id { get; set; }
public string IMEI { get; set; }
public virtual DeviceVerizon VerizonData { get; set; }
}
public class DeviceVerizon
{
public int DeviceId { get; set; }
public virtual Device Device { get; set; }
public string ServicePlan { get; set; }
public string State { get; set; }
}
public class DeviceVerizonMap : IEntityTypeConfiguration<DeviceVerizon>
{
public void Configure(EntityTypeBuilder<DeviceVerizon> builder)
{
builder.ToTable(nameof(DeviceVerizon));
builder.HasKey(d => d.DeviceId);
builder.HasOne(o => o.Device)
.WithOne(o => o.VerizonData)
.HasForeignKey<DeviceVerizon>(a => a.DeviceId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
;
}
}
public partial class DeviceMap : IEntityTypeConfiguration<Device>
{
public override void Configure(EntityTypeBuilder<Device> builder)
{
builder.ToTable(nameof(Device));
builder.HasKey(d => d.Id);
builder.HasIndex(d => d.IMEI).IsUnique();
}
}
so, DeviceId is PR and FK for DeviceVerizon.
I want to reassign DeviceVerizon from one to another record of Device.
I tried to it just change DeviceId from source Device to target device and no success: EF 3.x: Change Primary key value
Ok, I try to create copy of DeviceVerizon data by the following way:
CreateMap<DeviceVerizon, DeviceVerizon>()
.ForMember(d => d.DeviceId, o => o.Ignore())
;
and then:
var newVerizonData = _mapper.Map<DeviceVerizon, DeviceVerizon>(verizonData);
newVerizonData.DeviceId = targetDeviceId;
(also, I tried targetDevice.VerizonData = newVerizonData; instead of newVerizonData.DeviceId = targetDeviceId;), in debugger I see, that targetDevice.VerizonData has expected data and that object has expected DeviceId (targetDeviceId), but after await _context.SaveChangesAsync(); I don't have this verizon data at all in DB!
What is wrong?
Problem was because Automapper maps also Device entity and no matter, set you DeviceId or not... This solution works:
CreateMap<DeviceVerizon, DeviceVerizon>()
.ForMember(d => d.DeviceId, o => o.Ignore())
.ForMember(d => d.Device, o => o.Ignore())
;
My problem is similar to Is it possible to have a relation where the foreign key is also the primary key? but I have to do this with Fluent API.
I have basically the same situation as described in the question, but I cannot use annotations on my domain models due to our coding standards. Here is a bit of code (Clarified):
Domain Classes:
public class Table1
{
public long ID { get; set; }
public int SubTableType { get; set; }
...
public Table2 Table2 { get; set; }
public Table3 Table3 { get; set; }
public List<Table4> Table4s { get; set; }
public List<Table5> Table5s { get; set; }
}
public class Table2
{
public long ID { get; set; }
public string Location { get; set; }
public string Task { get; set; }
...
public Table1 Table1 { get; set; }
public Table6 Table6 { get; set; }
public List<Table7> Table7s { get; set; }
}
public class Table3
{
public long ID { get; set; }
public string DescriptionAndLocation { get; set; }
...
public Table1 Table1 { get; set; }
}
Configuration Classes:
internal class Table1Configuration : EntityTypeConfiguration<Table1>
{
public Table1Configuration()
{
ToTable("Table1");
HasKey(so => so.ID);
Property(so => so.SubTableType)
.IsRequired();
Property(so => so.ID)
.IsRequired()
.HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);
...
}
}
internal class Table2Configuration : EntityTypeConfiguration<Table2>
{
public Table2Configuration()
{
ToTable("Table2");
HasKey(bc => bc.ID);
Property(bc => bc.ID)
.IsRequired();
Property(bc => bc.Location)
.IsOptional()
.HasColumnType("nvarchar")
.HasMaxLength(50);
Property(bc => bc.Task)
.IsOptional()
.HasColumnType("nvarchar")
.HasMaxLength(4000);
...
HasRequired(bc => bc.Table1)
.WithOptional(so => so.Table2);
HasRequired(bc => bc.Table8)
.WithMany(bot => bot.Table2s)
.HasForeignKey(bc => bc.Tabe8ID);
}
}
internal class Table3Configuration : EntityTypeConfiguration<Table3>
{
public Table3Configuration()
{
ToTable("Table3");
HasKey(hic => hic.ID);
Property(hic => hic.DescriptionAndLocation)
.IsOptional()
.HasColumnType("nvarchar")
.HasMaxLength(4000);
Property(hic => hic.ID)
.IsRequired()
.HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);
HasRequired(hic => hic.Table1)
.WithOptional(so => so.Table3);
}
}
When I run this code I get the error:
Invalid column name 'Table2_ID'.
What you are asking is the so called Shared Primary Key Associations, which is the standard (and better supported) EF6 model for one-to-one relationships.
Rather than removing the ID property, you should remove the MapKey call which is used to define a shadow FK property (which you don't need).
Since the property called ID by convention is a PK and required, basically all you need is this:
HasRequired(hic => hic.Table1)
.WithOptional(so => so.Table2); // or Table3
or the explicit equivalent of [Key] / [ForeignKey] combination:
HasKey(hic => hic.ID);
HasRequired(hic => hic.Table1)
.WithOptional(so => so.Table2); // or Table3
Exactly as the example for Configuring a Required-to-Optional Relationship (One-to–Zero-or-One) from the documentation.
I would try something like this:
modelBuilder.Entity<Table1>().HasKey(t => t.ID);
modelBuilder.Entity<Table1>().Property(t =>t.ID)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Table1>()
.HasOptional(t1 => t1.Table2)
.WithRequired(t2 => t2.Table1).Map(m => m.MapKey("ID"));
I am using NHibernate. This is the employee class
public class Employee
{
public virtual int Id { get; protected set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual Store Store { get; set; }
}
This is the store class:
public class Store
{
public virtual int Id { get; protected set; }
public virtual string Name { get; set; }
public virtual IList<Employee> Staff { get; set; }
public Store()
{
Staff = new List<Employee>();
}
}
The following are the mapping classes. Employee Map:
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap ()
{
Id(x => x.Id);
Map(x => x.FirstName);
Map(x => x.LastName);
References(x => x.Store);
}
}
Store Map:
public class StoreMap:ClassMap<Store>
{
public StoreMap()
{
Id(x => x.Id);
Map(x => x.Name);
HasMany(x => x.Staff);
// HasManyToMany(x => x.Products).Cascade.All();
//.Table("StoreProduct");
}
}
When I run this code:
using (session.BeginTransaction())
{
var stores = session.CreateCriteria(typeof(Store)).List<Store>();
//for (int i=0; i<stores.Count;)
//{
// Response.Write(st
//}
foreach (var item in stores)
{
Response.Write(item.Staff.ToList());
}
}
I receive the following error:
could not initialize a collection: [test.Models.Store.Staff#1][SQL:
SELECT staff0_.Store_id as Store4_1_, staff0_.Id as Id1_, staff0_.Id
as Id0_0_, staff0_.LastName as LastName0_0_, staff0_.FirstName as
FirstName0_0_, staff0_.Store_id as Store4_0_0_ FROM [Employee] staff0_
WHERE staff0_.Store_id=?]
You code seems to work fine for me. But I'm generating an empty scheme from scratch, don't know if you are maybe missing something or if the id references are set correctly.
If you do not specify the reference Id column for staff->Store it uses Sore_id as you can see in your query text. Does this column exist?
Anyways, if it is still not working for you, try to explicitly define a name for the .KeyColumn for your HasMany(x => x.Staff); mapping
:edit:
You need to change StoreMap to have HasMany(x => x.Staff).KeyColumn("store");
And you need to change EmployeeMap to have References(x => x.Store).Columns("store");
So that both sides link to the same reference column...
I've already spent the last few days trying to fix my problem, sadly without any result. I've already read countless post on here on this subject, but I keep getting the same error. "Unknown column 'Extent1.foo_id' in 'field list'"... What am I doing wrong? My mapping has to be wrong some way, but I fail to see how...
Edit: It's database first!
I also have another class "Doo" which has a many to many relationship with "Foo" but that one is working fine.
Thanks in advance!
public class Foo
{
public Foo()
{
this.FooBoo = new Collection<FooBoo>();
}
public String FooId { get; set; }
public virtual ICollection<FooBoo> FooBoo { get; set; }
}
public class Boo
{
public Boo()
{
this.FooBoo = new Collection<FooBoo>();
}
public String BooId { get; set; }
public virtual ICollection<FooBoo> FooBoo { get; set; }
}
public class FooBoo
{
public String Fooid { get; set; }
public virtual Foo Foo { get; set; }
public String Booid { get; set; }
public virtual Boo Boo { get; set; }
public Boolean RandomProperty { get; set; }
}
public class BooMapper : EntityTypeConfiguration<Boo>
{
public BooMapper()
{
this.HasKey(t => t.BooId);
this.Property(t => t.BooId).HasColumnName("booid");
this.ToTable("boo", "fooboodb");
this.HasMany(t => t.FooBoo)
.WithRequired()
.HasForeignKey(t => t.Booid);
}
}
public class FooMapper : EntityTypeConfiguration<Foo>
{
public FooMapper()
{
this.HasKey(t => t.FooId);
this.Property(t => t.FooId).HasColumnName("fooid");
.
this.ToTable("foo", "fooboodb");
this.HasMany(t => t.FooBoo)
.WithRequired()
.HasForeignKey(t => t.Booid);
}
}
public class FooBooMapper : EntityTypeConfiguration<FooBoo>
{
public FooBooMapper()
{
this.HasKey(t => new {t.Fooid, t.Booid});
this.Property(t => t.Fooid);
this.Property(t => t.Booid);
this.Property(t => t.RandomProperty);
this.ToTable("fooboo", "fooboodb");
this.Property(t => t.Fooid).HasColumnName("Fooid");
this.Property(t => t.Booid).HasColumnName("Booid");
this.Property(t => t.RandomProperty).HasColumnName("randomproperty");
}
}
You must provide a lambda expression for the two WithRequired calls in order to specify the inverse navigation properties. Otherwise EF will assume that they belong to another additional relationship which is causing those foreign keys with underscores:
public class BooMapper : EntityTypeConfiguration<Boo>
{
public BooMapper()
{
//...
this.HasMany(t => t.FooBoo)
.WithRequired(fb => fb.Boo)
.HasForeignKey(t => t.Booid);
}
}
public class FooMapper : EntityTypeConfiguration<Foo>
{
public FooMapper()
{
//...
this.HasMany(t => t.FooBoo)
.WithRequired(fb => fb.Foo)
.HasForeignKey(t => t.Booid);
}
}
i'm new to nhibernate so maybe the response depends on my lack of knowledge.
I created these two tables:
(sorry for italian language, i hope you can understand withouth any problems).
Then, i have these object in my model:
[Serializable]
public class Profilo
{
public virtual int Id { get; set; }
public virtual string Matricola { get; set; }
public virtual string Ruolo { get; set; }
public virtual IList ListaSedi { get; set; }
public Profilo()
{
ListaSedi = new List();
}
}
[Serializable]
public class Sede
{
public virtual string CodiceSede { get; set; }
public virtual string DescrizioneSede { get; set; }
public virtual Profilo Profilo { get; set; }
}
This is the way i mapped entities using fluent nhibernate:
public class Map_Sede : FluentNHibernate.Mapping.ClassMap
{
public Map_Sede()
{
Table("TBA_Sede");
Id(x => x.CodiceSede).Column("codice_sede").GeneratedBy.Assigned();
Map(x => x.DescrizioneSede)
.Column("descrizione");
References(prof => prof.Profilo)
.Column("codice_sede");
}
}
public class Map_Profilo : FluentNHibernate.Mapping.ClassMap
{
public Map_Profilo()
{
Table("TBA_Profilo");
Id(x => x.Id).Column("id").GeneratedBy.Identity();
Map(x => x.Matricola)
.Column("matricola");
Map(x => x.Ruolo)
.Column("ruolo");
HasMany(x => x.ListaSedi)
.AsBag()
.KeyColumns.Add("codice_sede")
.Not.LazyLoad()
.Cascade.None();
}
}
Now, i'd like to insert a new Profilo instance on my. Everything seems to work but nhibernate does not insert values on TBA_Profilo.codice_sede column. I noticed that the insert statement is composed by two parameters (matricola, ruolo) - why does it forget the third parameter?
I read somewhere (on nhibernate mailing list) that's quite normal 'cause nhiberate insert values with null first and then update the same records with right values contained in the list property.
Is it right?
Am i doing any errors?
I hope to have clarified the situation.
thx guys
ps: I'm using Nhibernate 2.1 and fluent nhibernate 1.1
UPDATE: This is the code i use to save entity.
var sesionFactory = NHibernateHelper.createSessionFactory();
using (NHibernate.ISession session = sesionFactory.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
try
{
session.SaveOrUpdate(entity);
transaction.Commit();
session.Flush();
}
catch (Exception)
{
transaction.Rollback();
throw;
}
finally
{
transaction.Dispose();
}
}
}
UPDATE 2: Following sly answer i slightly modified my solution. These are new model entities:
[Serializable]
public class Profilo
{
public virtual int Id { get; set; }
public virtual string Matricola { get; set; }
public virtual string Ruolo { get; set; }
public virtual IList ListaSedi { get; set; }
public Profilo()
{
ListaSedi = new List();
}
}
[Serializable]
public class Sede
{
public virtual string CodiceSede { get; set; }
public virtual string DescrizioneSede { get; set; }
public virtual IList ProfiliAssociati { get; set; }
}
And new mappings:
public class Map_Sede : FluentNHibernate.Mapping.ClassMap
{
public Map_Sede()
{
Table("TBA_Sede");
Id(x => x.CodiceSede).Column("codice_sede").GeneratedBy.Assigned();
Map(x => x.DescrizioneSede)
.Column("descrizione");
HasMany(x => x.ProfiliAssociati)
.AsBag()
.KeyColumns.Add("codice_sede")
.Cascade.All();
}
}
public class Map_Profilo : FluentNHibernate.Mapping.ClassMap
{
public Map_Profilo()
{
Table("TBA_Profilo");
Id(x => x.Id).Column("id").GeneratedBy.Identity();
Map(x => x.Matricola)
.Column("matricola");
Map(x => x.Ruolo)
.Column("ruolo");
HasMany(x => x.ListaSedi)
.AsBag()
.Inverse()
.KeyColumns.Add("codice_sede")
.Cascade.SaveUpdate();
}
}
Now it seems quite to work: i mean that nhibernate can write TBA_Profilo.codice_sede column even if it doesn't cicle on Profilo.IList (it inserts the last element of this List).
Any ideas?
KeyColumns mapping is applied only child table in many-to-one connection.
If you want to have connection you will need to use Id column from TBA_portfolio table and reference it from TBA_Sede.
Something like this:
Tba_portfolio
Id|matricola|ruolo
Tba_sede
Id|PorfolioId|descrizione
Your mapping is wrong, try this:
public class Map_Profilo : FluentNHibernate.Mapping.ClassMap
{
public Map_Profilo()
{
Table("TBA_Profilo");
Id(x => x.Id).Column("id").GeneratedBy.Identity();
Map(x => x.Matricola)
.Column("matricola");
Map(x => x.Ruolo)
.Column("ruolo");
HasMany(x => x.ListaSedi)
.AsBag()
.KeyColumns.Add("codice_sede")
.Not.LazyLoad()
.Cascade.SaveUpdate();
}
}
public class Map_Sede : FluentNHibernate.Mapping.ClassMap
{
public Map_Sede()
{
Table("TBA_Sede");
Id(x => x.CodiceSede).Column("codice_sede").GeneratedBy.Assigned();
Map(x => x.DescrizioneSede)
.Column("descrizione");
References(prof => prof.Profilo)
.Column("codice_sede")
.Cascade.None()
.Inverse();
}
}
The key is, you need the parent profilio to save its child items. Have the child items do nothing to its parent. Also, your table diagram looks wrong, profolio should not have a key to sede, but sede should have a fk to profolio.