Fluent nhibernate one to one mapping - c#

How do I make a one to one mapping.
public class Setting
{
public virtual Guid StudentId { get; set; }
public virtual DateFilters TaskFilterOption { get; set; }
public virtual string TimeZoneId { get; set; }
public virtual string TimeZoneName { get; set; }
public virtual DateTime EndOfTerm { get; set; }
public virtual Student Student { get; set; }
}
Setting Class map:
public SettingMap()
{
// Id(Reveal.Member<Setting>("StudentId")).GeneratedBy.Foreign("StudentId");
//Id(x => x.StudentId);
Map(x => x.TaskFilterOption)
.Default(DateFilters.All.ToString())
.NvarcharWithMaxSize()
.Not.Nullable();
Map(x => x.TimeZoneId)
.NvarcharWithMaxSize()
.Not.Nullable();
Map(x => x.TimeZoneName)
.NvarcharWithMaxSize()
.Not.Nullable();
Map(x => x.EndOfTerm)
.Default("5/21/2011")
.Not.Nullable();
HasOne(x => x.Student);
}
Student Class map
public class StudentMap: ClassMap<Student>
{
public StudentMap()
{
Id(x => x.StudentId);
HasOne(x => x.Setting)
.Cascade.All();
}
}
public class Student
{
public virtual Guid StudentId { get; private set; }
public virtual Setting Setting { get; set; }
}
Now every time I try to create a settings object and save it to the database it crashes.
Setting setting = new Setting
{
TimeZoneId = viewModel.SelectedTimeZone,
TimeZoneName = info.DisplayName,
EndOfTerm = DateTime.UtcNow.AddDays(-1),
Student = student
};
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Settings_Students". The conflict occurred in database "Database", table "dbo.Students", column 'StudentId'.
The statement has been terminated.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Data.SqlClient.SqlException: The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Settings_Students". The conflict occurred in database "Database", table "dbo.Students", column 'StudentId'.
The statement has been terminated.
What am I missing?
Edit
public class StudentMap: ClassMap<Student>
{
public StudentMap()
{
Id(x => x.StudentId)
.GeneratedBy.Guid();
HasOne(x => x.Setting)
.PropertyRef("Student")
.Cascade.All();
}
}
public class SettingMap: ClassMap<Setting>
{
public SettingMap()
{
Id(x => x.StudentId)
.GeneratedBy.Guid();
Map(x => x.TaskFilterOption)
.Default(DateFilters.All.ToString())
.NvarcharWithMaxSize().Not.Nullable();
Map(x => x.TimeZoneId)
.NvarcharWithMaxSize().Not.Nullable();
Map(x => x.TimeZoneName)
.NvarcharWithMaxSize().Not.Nullable();
Map(x => x.EndOfTerm)
.Default("5/21/2011").Not.Nullable();
References(x => x.Student).Unique();
}
}
Setting setting = new Setting
{
TimeZoneId = viewModel.SelectedTimeZone,
TimeZoneName = info.DisplayName,
EndOfTerm = DateTime.UtcNow.AddDays(-1),
Student = student
};
studentRepo.SaveSettings(setting);
studentRepo.Commit();
I get these error for both ways
Invalid index 5 for this SqlParameterCollection with Count=5. Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.IndexOutOfRangeException: Invalid index 5 for this SqlParameterCollection with Count=5. Source Error: Line 76: using (ITransaction transaction = session.BeginTransaction()) Line 77: { Line 78: transaction.Commit(); Line 79: } Line 80: }

There are two basic ways how to map bidirectional one-to-one association in NH. Let's say the classes look like this:
public class Setting
{
public virtual Guid Id { get; set; }
public virtual Student Student { get; set; }
}
public class Student
{
public virtual Guid Id { get; set; }
public virtual Setting Setting { get; set; }
}
Setting class is a master in the association ("aggregate root"). It is quite unusual but it depends on problem domain...
Primary key association
public SettingMap()
{
Id(x => x.Id).GeneratedBy.Guid();
HasOne(x => x.Student).Cascade.All();
}
public StudentMap()
{
Id(x => x.Id).GeneratedBy.Foreign("Setting");
HasOne(x => x.Setting).Constrained();
}
and a new setting instance should be stored:
var setting = new Setting();
setting.Student = new Student();
setting.Student.Name = "student1";
setting.Student.Setting = setting;
setting.Name = "setting1";
session.Save(setting);
Foreign key association
public SettingMap()
{
Id(x => x.Id).GeneratedBy.Guid();
References(x => x.Student).Unique().Cascade.All();
}
public StudentMap()
{
Id(x => x.Id).GeneratedBy.Guid();
HasOne(x => x.Setting).Cascade.All().PropertyRef("Student");
}
Primary key association is close to your solution. Primary key association should be used only when you are absolutely sure that the association will be always one-to-one. Note that AllDeleteOrphan cascade is not supported for one-to-one in NH.
EDIT: For more details see:
http://fabiomaulo.blogspot.com/2010/03/conform-mapping-one-to-one.html
http://ayende.com/blog/3960/nhibernate-mapping-one-to-one

Here a complete sample with foreign key association
using System;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using FluentNHibernate.Mapping;
namespace NhOneToOne
{
public class Program
{
static void Main(string[] args)
{
try
{
var sessionFactory = Fluently.Configure()
.Database(
MsSqlConfiguration.MsSql2005
.ConnectionString(#"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=NHTest;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False")
.ShowSql()
)
.Mappings(m => m
.FluentMappings.AddFromAssemblyOf<Program>())
.BuildSessionFactory();
ISession session = sessionFactory.OpenSession();
Parent parent = new Parent();
parent.Name = "test";
Child child = new Child();
child.Parent = parent;
parent.Child = child;
session.Save(parent);
session.Save(child);
int id = parent.Id;
session.Clear();
parent = session.Get<Parent>(id);
child = parent.Child;
}
catch (Exception e)
{
Console.Write(e.Message);
}
}
}
public class Child
{
public virtual string Name { get; set; }
public virtual int Id { get; set; }
public virtual Parent Parent { get; set; }
}
public class Parent
{
public virtual string Name { get; set; }
public virtual int Id { get; set; }
public virtual Child Child { get; set; }
}
public class ChildMap : ClassMap<Child>
{
public ChildMap()
{
Table("ChildTable");
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.Name);
References(x => x.Parent).Column("IdParent");
}
}
public class ParentMap : ClassMap<Parent>
{
public ParentMap()
{
Table("ParentTable");
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.Name);
HasOne(x => x.Child).PropertyRef(nameof(Child.Parent));
}
}
}
And the SQL to create tables
CREATE TABLE [dbo].[ParentTable] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[Name] VARCHAR (MAX) NULL
);
CREATE TABLE [dbo].[ChildTable] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[IdParent] INT NOT NULL,
[Name] VARCHAR (50) NULL
);
ALTER TABLE [dbo].[ChildTable]
ADD CONSTRAINT [FK_ChildTable_ToTable] FOREIGN KEY ([IdParent]) REFERENCES [dbo].[ParentTable] ([Id]);

First, define one of the sides of the relationship as Inverse(), otherwise there is a redundant column in the database and this may cause the problem.
If this doesn't work, output the SQL statements generated by NHibernate (using ShowSql or through log4net) and try to understand why the foreign key constraint is violated (or post it here with the SQL, and don't forget the values of the bind variables that appear afer the SQL statement).

You should not define the StudentId in Sesstings class. Sessting class already has it (from
public virtual Student Student { get; set; } ). Probably it should be SesstingId and you should map the Id field as well (you have to define/map the primary key).

Related

Saving a list with a reference in nHibernate

I need to save a list of DataNode objects that have a property refering a NodeType objects, but I've tried all kinds of mapping settings, but keep getting errors.
The classes look as followed:
public class DataNode
{
public virtual int Id { get; set; }
public virtual string Value { get; set; }
public virtual NodeType NodeType { get; set; }
}
public class NodeType
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
This is the mapping for the DataNode
public class DataNodeMapping : ClassMap<DataNode>
{
protected readonly string _tableName;
public DataNodeMapping()
: this("ClientDataNode")
{
}
protected DataNodeMapping(string table)
{
_tableName = (table.StartsWith("[") ? table : ($"[{table}]"));
Table(_tableName);
Id(x => x.Id).GeneratedBy.Assigned();
Map(x => x.Value);
HasOne(x => x.NodeType)
.ForeignKey("ClientNodeTypeId")
.PropertyRef(p => p.Id)
.Cascade.AllDeleteOrphan()
.LazyLoad();
OptimisticLock.Version();
}
}
The actual save function looks as follows:
public async Task Execute()
{
var apiDataNodes = await _apiService.GetDataNodes();
var dbDataNodes = await _session.Query<DataNode>().ToListAsync();
using (var transaction = _session.BeginTransaction())
{
foreach (var dto in apiDataNodes.Where(api => !dbDataNodes.Any(db => db.Id == api.Id)))
{
var datanode = _mapper.Map<DataNode>(dto);
await _session.SaveAsync(datanode);
}
transaction.Commit();
}
}
And this results in the following exception:
a different object with the same identifier value was already associated with the session: 203, of entity: ....NodeType
Removing the LazyLoad attribite didn't help either
The nodetype table is already seeded and every DataNode object has a valid value for DataNode.NodeType.Id
There's a one-to-many relationship between DataNode and NodeType.
I've been struggling for hours to get the correct mapping settings, tried several other options like:
References(x => x.NodeType)
.Column("ClientNodeTypeId")
.PropertyRef(p => p.Id);
Resulting in:
Error dehydrating property value for ......NodeType
=> Unable to resolve property: Id
Adding .ReadOnly() to that line ends up in storing the reccords, but with a NULL for every ClientNodeTypeId
Also tried:
Map(x => x.NodeType.Id, "ClientNodeTypeId");
But that resulted in a foreign key constraint error:
The INSERT statement conflicted with the FOREIGN KEY constraint "ClientDataNode_ClientNodeTypeId_ClientNodeType_Id".
Please help, I have no ideas left and spend way too much time already on what shouldn't be a big problem.

Code First With Existing Database, Entity Framework: Cannot insert explicit value for identity column in table when IDENTITY_INSERT is set to OFF

First off, please do not mark this as duplicate. I know it has been touched several times, but I believe my situation is different. I'm doing a code first approach where the database essentially already existed in some for or another as well.
When I call DbContext.SaveChanges() I receive the following error: "Cannot insert explicit value for identity column in table 'CustomerShippingConfiguration' when IDENTITY_INSERT is set to OFF". As you can see in the generated SQL below, Entity Framework is trying to insert 0 into the Id column for a new record.
INSERT [dbo].[CustomerShippingConfiguration]([Id], [CustomerId], [AverageCartonWeight], [AverageCartonsPerPallet], [CreatedBy], [Created], [UpdatedBy], [Updated])
VALUES (#0, #1, #2, #3, #4, #5, #6, #7)
-- #0: '0' (Type = Int32)
-- #1: '119' (Type = Int32)
-- #2: '11' (Type = Decimal, Precision = 18, Scale = 2)
-- #3: '11' (Type = Int32)
-- #4: '616' (Type = Int32)
-- #5: '8/9/2016 10:09:08 AM' (Type = DateTime)
-- #6: '616' (Type = Int32)
-- #7: '8/9/2016 10:09:08 AM' (Type = DateTime)
This Id column is of type INT and setup as the identity column, primary key, and auto increments.
The entity model looks like this:
public class ShippingConfigurationEntity : EntityBase<ShippingConfigurationEntity>, IEntity
{
public int CustomerId { get; set; }
public virtual CustomerEntity Customer { get; set; }
public decimal? AverageCartonWeight { get; set; }
public int? AverageCartonsPerPallet { get; set; }
public virtual ICollection<ShippingAddressEntity> Addresses { get; set; }
public ShippingConfigurationEntity()
{
Addresses = new List<ShippingAddressEntity>();
}
}
public abstract class EntityBase<T>
{
public virtual int Id { get; set; }
public virtual int CreatedBy { get; set; }
public virtual DateTime Created { get; set; }
public virtual int UpdatedBy { get; set; }
public virtual DateTime Updated { get; set; }
}
And the configuration for the entity looks like this:
public class ShippingConfigurationConfiguration : EntityTypeConfiguration<ShippingConfigurationEntity>
{
public ShippingConfigurationConfiguration()
{
HasKey(t => t.Id);
Property(t => t.CustomerId).IsRequired();
Property(t => t.AverageCartonsPerPallet).IsOptional();
Property(t => t.AverageCartonWeight).IsOptional();
Property(t => t.CreatedBy).IsRequired();
Property(t => t.Created).IsRequired();
Property(t => t.UpdatedBy).IsRequired();
Property(t => t.Updated).IsRequired();
ToTable("CustomerShippingConfiguration");
HasMany(x => x.Addresses).WithRequired(x => x.ShippingConfiguration).HasForeignKey(x => x.ShippingConfigurationId).WillCascadeOnDelete();
}
}
There is a parent entity called Customer that looks similar to this:
public class CustomerEntity : EntityBase<CustomerEntity>, IEntity
{
public int LocationID { get; set; }
public virtual ShippingConfigurationEntity ShippingConfiguration { get; set; }
}
Where the configuration looks like this:
class CustomerConfiguration : EntityTypeConfiguration<CustomerEntity>
{
public CustomerConfiguration()
{
HasKey(t => t.Id);
Property(t => t.LocationID).IsRequired();
Ignore(t => t.Created);
Ignore(t => t.CreatedBy);
Ignore(t => t.Updated);
Ignore(t => t.UpdatedBy);
ToTable("Customer");
Property(t => t.Id).HasColumnName("ID");
Property(t => t.LocationID).HasColumnName("LOCATION_ID");
HasOptional(x => x.ShippingConfiguration).WithRequired(x => x.Customer).WillCascadeOnDelete();
}
}
If I remove the relationship between CustomerEntity and ShippingConfiguration Entity, everythings if I use the following for the Id property on ShippingConfiguration:
Property(t => t.Id).IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
I have another entity which is setup in a very similar pattern that works just fine. I'm really at a loss here. Thank you in advance for any advice.
You need to tell your configuration that the database will handle key generation.
using System.ComponentModel.DataAnnotations.Schema;
public class ShippingConfigurationConfiguration : EntityTypeConfiguration<ShippingConfigurationEntity>
{
public ShippingConfigurationConfiguration()
{
HasKey(t => t.Id);
Property(a => a.Id)
.IsRequired()
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}

EntiyFramework-Update composite key value in join table with TryGetObjectByKey

I‘m using Entity framework code first ,the following model is a join table with composite key
Model code
public class Schedule
{
public int BabyId { get; set; }
public int VaccineId { get; set; }
public Baby Baby { get; set; }
public Vaccine Vaccine { get; set; }
public DateTime Time { get; set; }
}
Entity framework config code
public sealed class ScheduleConfig : EntityTypeConfiguration<Schedule>
{
public ScheduleConfig()
{
HasKey(q =>
new
{
q.BabyId,
q.VaccineId
});
HasRequired(t => t.Baby)
.WithMany(t => t.Schedules)
.HasForeignKey(t => t.BabyId);
HasRequired(t => t.Vaccine)
.WithMany(t => t.Schedules)
.HasForeignKey(t => t.VaccineId);
ToTable("Schedule", "dbo");
}
}
the following code update tables in database
public bool Update(T t)
{
var entityName = GetEntityName<T>();
object originalItem;
var key = ((IObjectContextAdapter)Context).ObjectContext.CreateEntityKey(entityName, t);
if (((IObjectContextAdapter)Context).ObjectContext.TryGetObjectByKey(key, out originalItem))
{
((IObjectContextAdapter)Context).ObjectContext.ApplyCurrentValues(key.EntitySetName, t);
}
Context.SaveChanges();
return true;
}
The problem is when I change one of key values ,the code inside If block in update method never execute and the table never updates.
My question is should I change my Model or is there any better code that can handle this problem.
Any idea?

NHibernate Envers: Cannot insert duplicate key in object

I'm using Envers to audit tables, but it's creating some audit tables for unknown/absent tables.
It's looks like a Many To Many relation audit table for Many To One relations.
Is this right? If it's, Why?
dbo.HorarioFixo - OK
dbo.HorarioFixo_Auditoria - OK
dbo.HorarioFixo_JanelaHorarioFixo_Auditoria - NOK
dbo.JanelaHorarioFixo - OK
dbo.JanelaHorarioFixo_Auditoria - OK
But when I try to remove/delete and HorarioFixo I'm getting an error.
The error I'm getting:
NHibernate.Exceptions.GenericADOException
could not execute batch command.[SQL: SQL not available]
em NHibernate.Engine.ActionQueue.BeforeTransactionCompletionProcessQueue.BeforeTransactionCompletion()
em NHibernate.Impl.SessionImpl.BeforeTransactionCompletion(ITransaction tx)
em NHibernate.Transaction.AdoTransaction.Commit()
em Foo.Testes.Servicos.TesteCanalDeTransmissaoService.RemoveDependenciasCorretamente() na TesteCanalDeTransmissaoService.cs: line 195
System.Data.SqlClient.SqlException
Violation of PRIMARY KEY constraint 'PK__HorarioF__450088476960C81E'. Cannot insert duplicate key in object 'dbo.HorarioFixo_JanelaHorarioFixo_Auditoria'.
Violation of PRIMARY KEY constraint 'PK__HorarioF__450088476960C81E'. Cannot insert duplicate key in object 'dbo.HorarioFixo_JanelaHorarioFixo_Auditoria'.
The statement has been terminated.
The statement has been terminated.
This is the SQL duplicated:
exec sp_executesql N'INSERT INTO HorarioFixo_JanelaHorarioFixo_Auditoria (REVTYPE, REV, HorarioFixoId, JanelaHorarioFixoId) VALUES (#p0, #p1, #p2, #p3)',N'#p0 tinyint,#p1 int,#p2 bigint,#p3 bigint',#p0=2,#p1=3,#p2=1,#p3=2 go
All this is a part of the code. If you need something more, leave a comment.
My classes:
public class Entidade
{
protected Entidade();
public virtual long Id { get; set; }
public virtual long Version { get; set; }
public abstract override bool Equals(object obj);
public override int GetHashCode();
}
public class Horario : Entidade
{
protected Horario()
{
}
}
public class HorarioFixo : Horario
{
public virtual int Frequencia { get; set; }
public virtual ICollection<JanelaHorarioFixo> JanelasRemessa { get; set; }
public virtual ICollection<JanelaHorarioFixo> JanelasRetorno { get; set; }
}
public class JanelaHorarioFixo : Entidade
{
public virtual TimeSpan HorarioInicio { get; set; }
public virtual TimeSpan? HorarioLimite { get; set; }
}
My mappings:
public class HorarioMap : ClassMapping<Horario>
{
public HorarioMap()
{
Id(x => x.Id, mapper =>
{
mapper.Generator(Generators.Identity);
mapper.UnsavedValue(0);
});
}
}
public class HorarioFixoMap : JoinedSubclassMapping<HorarioFixo>
{
public HorarioFixoMap()
{
Property(x => x.Frequencia);
Bag(x => x.JanelasRemessa, m =>
{
m.Cascade(Cascade.All);
m.Lazy(CollectionLazy.NoLazy);
}, map => map.OneToMany());
Bag(x => x.JanelasRetorno, m =>
{
m.Cascade(Cascade.All);
m.Lazy(CollectionLazy.NoLazy);
}, map => map.OneToMany());
}
}
public class JanelaHorarioFixoMap : ClassMapping<JanelaHorarioFixo>
{
public JanelaHorarioFixoMap()
{
Id(x => x.Id, mapper =>
{
mapper.Generator(Generators.Identity);
mapper.UnsavedValue(0);
});
Property(x => x.HorarioInicio, m => m.NotNullable(true));
Property(x => x.HorarioLimite, m => m.NotNullable(false));
}
}
NH and Envers configurations:
var ormHelper = ORMHelperUtils.GetORMHelper();
var mapper = new MyConventionModelMapper();
_config = new Configuration();
mapper.AddMappings(ormHelper.GetMappings());
mapper.AddMapping(typeof(REVINFOMap));
ormHelper.SetupApplicationNeeds(_config);
_config.AddMapping(mapper.CompileMappingForAllExplicitlyAddedEntities());
_config.SetProperty(Environment.CurrentSessionContextClass, "call");
if (ormHelper.UseEnvers)
{
var classesDominio = ormHelper.GetDomainTables();
if (classesDominio.Any())
{
var envers = new FluentConfiguration();
envers.Audit(classesDominio);
envers.SetRevisionEntity<REVINFO>(e => e.Id, e => e.Date, new CustomRevisionListener());
_config.SetEnversProperty(ConfigurationKey.AuditTableSuffix, "_Auditoria");
_config.IntegrateWithEnvers(envers);
}
}
I've just changed my class to
public class HorarioFixo : Horario
{
public virtual int Frequencia { get; set; }
public virtual ICollection<JanelaHorarioFixo> Janelas { get; set; }
}
And added a property to JanelaHorarioFixo to identify the type. But the table dbo.HorarioFixo_JanelaHorarioFixo_Auditoria is still there, and I don't know why.
If you use unidirectional one-to-many, Envers needs a link table to be able to have correct history.
If you use bidirectional one-to-many, no link table is needed.
See this answer.

Fluent nHibernate mapping problem

I have these tables/classes (example):
table FirstTable (
Id INTEGER NOT NULL DEFAULT AUTOINCREMENT,
Name VARCHAR(100) NOT NULL,
Document VARCHAR(20) NOT NULL
)
table SecondTable (
Id INTEGER NOT NULL,
Something VARCHAR(100) NULL,
FOREIGN KEY (Id) REFERENCES FirstTable (Id)
)
public class FirstClass {
public string Name { get; set; }
public string Document { get; set; }
public SecondClass SecondClass { get; set; }
}
public class SecondClass {
public string Something { get; set; }
public FirstClass FirstClass { get; set; }
}
The mapping is:
public class FirstClassMap : ClassMap<FirtsClass> {
Table("FirstTable");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Name);
Map(x => x.Document);
References(x => x.SecondClass, "Id").ForeignKey();
}
public class SecondClassMap : ClassMap<SecondClass> {
Table("SecondTable");
Id(x => x.Id).GeneratedBy.Foreign("FirstClass");
Max(x => x.Something);
HasOne(x => x.FirstClass).PropertyRef(x => x.SecondClass).Cascade.SaveUpdate();
}
FirstClass can have (0,1) SecondClass, and SecondClass can have (1,1) FirstClass.
The bellow code return the error "attempted to assign id from null one-to-one property: SecondClass"
var test = new SecondClass();
test.FirstClass = new FirstClass();
test.Something = "New test";
test.FirstClass.Name = "My name";
test.FirstClass.Document = "My document";
// ... commands to save.
It seems like NH is trying to save SecondClass first and fails to grab a generated ID from the not yet saved FirstClass.
Try to move the .Cascade.SaveUpdate() to the References declaration in FirstClassMap and call the save command on FirstClass.

Categories