I'm trying out a multilingual database with TPH. What I'm after is for there to only be one table that contains all strings (names, descriptions, etc.) in different languages.
EFCore can generate me an Sqlite database, but when I add this LanguageName, it throws an SqliteException.
await context.LanguageNames.AddAsync(
new LanguageName {
Id = LanguageName.EnglishLanguageNameId,
Value = LanguageName.EnglishLanguageNameValue,
ForeignEntityId = Language.EnglishLanguageId,
LanguageId = Language.EnglishLanguageId
});
await context.SaveChangesAsync();
The exception says: Unhandled exception. Microsoft.Data.Sqlite.SqliteException (0x80004005): SQLite Error 19: 'FOREIGN KEY constraint failed'. Googling wasn't any help in tackling this specific issue.
I've tried uploading the Sqlite file to an online IDE (this one, specifically) and executing the snippet below. It worked as expected.
INSERT INTO MultilingualString(id, Value, foreignentityid, languageid, discriminator)
VALUES (1, 'English', 1, 1, 'LanguageName')
So I tried running the same code in the DbContext.
await context.Database.ExecuteSqlRawAsync(
"INSERT INTO MultilingualString(id, value, foreignentityid, languageid, discriminator) " +
"VALUES (1, 'English', 1, 1, 'LanguageName')"
);
I still get the same SqliteException. What am I doing wrong?
In case more details are needed, here are the models I'm working with.
public record MultilingualString {
public UInt64 Id { get; init; }
public String Value { get; init; }
public UInt64 ForeignEntityId { get; init; }
public Language Language { get; init; }
public UInt64 LanguageId { get; init; }
protected internal static UInt64 GenerateId() { ... }
}
public record MultilingualString<TEntity> : MultilingualString
where TEntity : class {
public TEntity ForeignEntity { get; init; }
}
public record Language {
public UInt64 Id { get; init; }
public IEnumerable<LanguageName> Names { get; init; }
public IEnumerable<MultilingualString> StringValues { get; init; }
public static UInt64 EnglishLanguageId { get; } = 1;
}
public record LanguageName : MultilingualString<Language> {
public static String EnglishLanguageNameValue { get; } = "English";
public static UInt64 EnglishLanguageNameId { get; } = GenerateId();
}
Here's the model building:
protected override void OnModelCreating(ModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
BuildMultilingualStringModel(modelBuilder)
.BuildLanguageModel(modelBuilder)
.BuildItemModel(modelBuilder);
}
protected virtual DbContext BuildMultilingualStringModel(
ModelBuilder modelBuilder) {
modelBuilder.Entity<MultilingualString>(builder => {
builder.HasKey(#string => #string.Id);
builder.Property(#string => #string.Value).IsRequired();
});
modelBuilder.Entity<MultilingualString>()
.HasDiscriminator<String>("Discriminator")
.HasValue<LanguageName>(nameof(LanguageName))
.HasValue<ItemName>(nameof(ItemName))
.HasValue<ItemDescription>(nameof(ItemDescription));
return this;
}
protected virtual DbContext BuildLanguageModel(
ModelBuilder modelBuilder) {
modelBuilder.Entity<Language>(builder => {
builder.HasKey(language => language.Id);
builder.HasMany(language => language.Names)
.WithOne(name => name.ForeignEntity)
.HasForeignKey(name => name.ForeignEntityId);
builder.HasMany(language => language.StringValues)
.WithOne(#string => #string.Language)
.HasForeignKey(#string => #string.LanguageId);
builder.HasData(new Language {
Id = Language.EnglishLanguageId
});
});
return this;
}
protected virtual DbContext BuildItemModel(
ModelBuilder modelBuilder) {
modelBuilder.Entity<Item>(builder => {
builder.HasKey(item => item.Id);
builder.HasMany(item => item.Names)
.WithOne(name => name.ForeignEntity)
.HasForeignKey(name => (UInt32)name.ForeignEntityId);
builder.HasMany(item => item.Descriptions)
.WithOne(description => description.ForeignEntity)
.HasForeignKey(description => description.ForeignEntityId);
});
return this;
}
These are the other models in the database, just in case any of these have something to do with the error.
public record Item {
public UInt64 Id { get; init; }
public IEnumerable<ItemName> Names { get; init; }
public IEnumerable<ItemCategory> Categories { get; init; }
public IEnumerable<ItemDescription> Descriptions { get; init; }
}
public record ItemName : MultilingualString<Item> { }
public record ItemDescription : MultilingualString<Item> { }
EDIT: These are the create scripts:
CREATE TABLE "Items" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_Items" PRIMARY KEY AUTOINCREMENT
)
CREATE TABLE sqlite_sequence(name,seq)
CREATE TABLE "Languages" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_Languages" PRIMARY KEY AUTOINCREMENT
)
CREATE TABLE "MultilingualStrings" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_MultilingualStrings" PRIMARY KEY AUTOINCREMENT,
"Value" TEXT NOT NULL,
"ForeignEntityId" INTEGER NOT NULL,
"LanguageId" INTEGER NOT NULL,
"Discriminator" TEXT NOT NULL,
CONSTRAINT "FK_MultilingualStrings_Items_ForeignEntityId" FOREIGN KEY ("ForeignEntityId") REFERENCES "Items" ("Id") ON DELETE CASCADE,
CONSTRAINT "FK_MultilingualStrings_Languages_ForeignEntityId" FOREIGN KEY ("ForeignEntityId") REFERENCES "Languages" ("Id") ON DELETE CASCADE,
CONSTRAINT "FK_MultilingualStrings_Languages_LanguageId" FOREIGN KEY ("LanguageId") REFERENCES "Languages" ("Id") ON DELETE CASCADE
)
CREATE INDEX "IX_MultilingualStrings_ForeignEntityId" ON "MultilingualStrings" ("ForeignEntityId")
CREATE INDEX "IX_MultilingualStrings_LanguageId" ON "MultilingualStrings" ("LanguageId")
Related
Ok I think I may have got this wrong in the model builder. I want the case which can be one case but inside that one case they can be many relationships.
modelBuilder.Entity<RelationShips>()
.HasOne<MISObject>(s => s.Case)
.WithMany(g => g.RelationShip)
.HasForeignKey(s => s.MisObjectId);
But when I attempt to save with the above
DbUpdateException: An error occurred while updating the entries. See
the inner exception for details. SqlException: The INSERT statement
conflicted with the FOREIGN KEY constraint
"FK_RelationShips_MISobject_MisObjectId". The conflict occurred in
database "MISSystem", table "dbo.MISobject", column 'Id'. The
statement has been terminated.
My MISObject has a collection of relationships as such
public class MISObject {
public int Id { get; set; }
public ICollection<RelationShips> RelationShip { get; set; }
}
public class RelationShips {
public int Id { get; set; }
public MISObject Case { get; set; }
}
Will the above not allow a one to many relationship basically one case could have 20 relationships but there could be many cases. But those 20 relationships should only belong to that case if that makes since.
I think I need to use hasmany but not to sure how in this context.
Edit 2
I think this might be what i need but im not sure how legal it is.
modelBuilder.Entity<MISObject>()
.HasMany(c => c.RelationShip);
modelBuilder.Entity<RelationShips>()
.HasMany(c => c.PersonOfIntrests);
modelBuilder.Entity<POI>()
.HasMany(c => c.PersonOfIntrestsPhotos)
.WithOne(e => e.PersonOfIntrest);
To make this work with the FluentAPI, you need to completely define the navigation and use a foreign key:
public class Relationship {
public int Id { get; set; }
public int MisObjectId { get; set; } // <-- add foreign key ID
public MisObject Case { get; set; }
}
public class Context : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MisObject>()
.HasMany(o => o.Relationships)
.WithOne(r => r.Case) // <-- add other endpoint
.HasForeignKey(r => r.MisObjectId); // <-- add FK property
}
}
Here is a fully working console project, that demonstrates these concepts:
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace IssueConsoleTemplate
{
public class MisObject {
public int Id { get; set; }
public ICollection<Relationship> Relationships { get; set; }
}
public class Relationship {
public int Id { get; set; }
public int MisObjectId { get; set; } // <-- add foreign key ID
public MisObject Case { get; set; }
}
public class Context : DbContext
{
public DbSet<MisObject> MisObjects { get; set; }
public DbSet<Relationship> Relationships { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(#"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=So62854210")
.UseLoggerFactory(
LoggerFactory.Create(
b => b
.AddConsole()
.AddFilter(level => level >= LogLevel.Information)))
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MisObject>()
.HasMany(o => o.Relationships)
.WithOne(r => r.Case) // <-- add other endpoint
.HasForeignKey(r => r.MisObjectId); // <-- add FK property
// Technically not necessary, because this navigation has already been defined
// in the previous line, but might be good practice anyway.
modelBuilder.Entity<Relationship>()
.HasOne(r => r.Case)
.WithMany(o => o.Relationships)
.HasForeignKey(r => r.MisObjectId);
}
}
internal static class Program
{
private static void Main()
{
using (var context = new Context())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
var newMisObject = new MisObject();
var newRelationships = new[]
{
new Relationship {Case = newMisObject},
new Relationship {Case = newMisObject},
new Relationship {Case = newMisObject},
};
context.MisObjects.Add(newMisObject);
context.Relationships.AddRange(newRelationships);
context.SaveChanges();
}
using (var context = new Context())
{
var misObjects = context.MisObjects
.Include(o => o.Relationships)
.ToList();
Debug.Assert(misObjects.Count == 1);
Debug.Assert(misObjects[0].Relationships.Count == 3);
}
}
}
}
Coincidentally, because the properties in this example follow EF Core naming conventions, you could even remove the OnModelCreating() method entirely here and the result would still work.
See Relationships: Conventions for further information on that.
Just use convention there is no need to use fluent config for relationships:
public class MISObject
{
public int Id { get; set; }
public ICollection<RelationShips> RelationShip { get; set; }
}
public class RelationShips
{
public int Id { get; set; }
public int CaseId { get; set; }
public MISObject Case { get; set; }
}
without any error, EF Core makes your Tables in the database.
Problem
I can not insert Data to a sql table with a foreign key to a IdentityColumn, in ef core 2.
Setup:
I have the following Entitie classes:
public class CourseMeeting
{
public int MID{ get; set; }
public int CID { get; set; }
public Course Course { get; set; }
public DateTime DateOfTheMeeting { get; set; }
public int MaxParticipants { get; set; }
public IList<CourseMeetingParticipant> CourseMeetingParticipants {get;set;}
}
public class CourseMeetingParticipant
{
public int MID{ get; set; }
public CourseMeeting CourseMeeting { get; set; }
public string SUID { get; set; }
public User Student { get; set; }
}
I map the classes to two SQL-Server tables with the Fluent API:
modelBuilder.Entity<CourseMeeting>(cm =>
{
cm.HasKey(k => k.MID);
cm.Property(p => p.MID)
.UseSqlServerIdentityColumn();
cm.HasMany(m => m.CourseMeetingParticipants)
.WithOne(m => m.CourseMeeting);
});
modelBuilder.Entity<CourseMeetingParticipant>(c =>
{
c.HasOne(m => m.CourseMeeting)
.WithMany(m => m.CourseMeetingParticipants)
.HasForeignKey(k => k.MID)
.HasPrincipalKey(p => p.MID);
c.Property(p => p.MID);
c.HasKey(k => new {k.MID, k.SUID});
});
The Tables:
CREATE TABLE [dbo].[CourseMeetingParticipants](
[MID] [int] NULL,
[SUID] [varchar](100) NOT NULL
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[CourseMeetingParticipants] WITH CHECK ADD FOREIGN KEY([MID])
REFERENCES [dbo].[CourseMeetings] ([MID])
alter table [dbo].[CourseMeetingParticipants]
ADD CONSTRAINT PK_CP PRIMARY KEY CLUSTERED (MID, SUID);
go
CREATE TABLE [dbo].[CourseMeetings]
(
[MID] INT IDENTITY(0,1) PRIMARY KEY
,[CID] INT FOREIGN KEY REFERENCES Courses (CID)
,[DateOfTheMeeting] datetime2 not null
,[MaxParticipants] int not null
)
When I try to add CourseMeetingParticipant to the table CourseMeetingParticipants I get the following Error:
InvalidOperationException: The property 'MID' on entity type 'CourseMeetingParticipant' has a temporary value. Either set a permanent value explicitly or ensure that the database is configured to generate values for this property.
This is the method I use to add the CourseMeetingParticipant:
[HttpPost]
[Authorize(Roles = "Student")]
public async Task<IActionResult> SignInToCourseMeeting(int id){
var participant = new CourseMeetingParticipant{
MID = id,
SUID = await _UserManger.GetUserIdAsync(await _UserManger.GetUserAsync(User)),
};
_db.CourseMeetingParticipants.Add(participant);
await _db.SaveChangesAsync();
return Redirect("/Course/Courses");
}
If i remove the HasOne, WithOne relations between the two entities i can add the CourseMeetingParticipant to the table with no problems. But in need the relations in other part of my Application. Have you any idea why this is not working?
Configuration:
ASP.NET Core 2.1
You already have the relationship for CourseMeeting <--> CourseMeetingParticipant defined in your CourseMeeting entity. What if you remove the redundant definition from CourseMeetingParticipant?
modelBuilder.Entity<CourseMeetingParticipant>(c =>
{
c.Property(p => p.MID);
c.HasKey(k => new {k.MID, k.SUID});
});
It may also make sense for you to make CourseMeetingParticipants.MID non-nullable. Then your table would look like:
CREATE TABLE [dbo].[CourseMeetingParticipants](
[MID] [int] NOT NULL,
[SUID] [varchar](100) NOT NULL
) ON [PRIMARY]
and your CourseMeeting entity would have a required property:
modelBuilder.Entity<CourseMeeting>(cm =>
{
cm.HasKey(k => k.MID);
cm.Property(p => p.MID)
.UseSqlServerIdentityColumn();
cm.HasMany(m => m.CourseMeetingParticipants)
.WithRequired(m => m.CourseMeeting);
});
How can I Include the relationships of properties on a collection where the collection type is of a Table per Hierarchy design and the relationships are defined on the derived types with lazy loading disabled?
What I am trying to avoid is to declare the relationships on the base type or to have multiple DB calls to retrieve the relationships. Is there a way around this?
Attempts and Errors
I have tried using OfType but receive this message.
System.ArgumentException - The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
I have tried using as but receive this error
System.InvalidOperationException - A specified Include path is not valid. The EntityType 'Sandbox.TPHMcve.Person' does not declare a navigation property with the name 'Buildings'.
Example
Please do not focus on the business details of this silly example, it is an mcve to illustrate the error I am receiving. My actual project has nothing to do with schools / school systems etc.
Structure
A school in a University has Persons
A Person is either Student or Teacher
Student has zero or more course books on loan
Teacher can be assigned as the care taker / guardian for one or more campus buildings
Code
SchoolService.cs
This is the actual code of interest that produces the Exceptions
public sealed class SchoolService
{
private readonly UniversityDbContext _dbContext;
public SchoolService(UniversityDbContext dbContext)
{
_dbContext = dbContext;
}
public School GetSchoolAndPeopleWithDetails_OfType(int schoolId)
{
return _dbContext.Schools.Where(x => x.SchoolId == schoolId)
.Include(x => x.EntitiesOfSchool.OfType<Teacher>().Select(y => y.Buildings))
.Include(x => x.EntitiesOfSchool.OfType<Student>().Select(y => y.BooksOnLoan))
.SingleOrDefault();
}
public School GetSchoolAndPeopleWithDetails_Cast(int schoolId)
{
return _dbContext.Schools.Where(x => x.SchoolId == schoolId)
.Include(x => x.EntitiesOfSchool.Select(y => (y as Teacher).Buildings))
.Include(x => x.EntitiesOfSchool.Select(y => (y as Student).BooksOnLoan))
.SingleOrDefault();
}
}
Entities.cs
public sealed class School
{
public int SchoolId { get; set; }
public string Name { get; set; }
public List<Person> EntitiesOfSchool { get; set; }
}
public abstract class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public School PrimarySchool { get; set; }
public int SchoolId { get; set; }
}
public sealed class Teacher : Person
{
public ICollection<Building> Buildings { get; set; }
}
public sealed class Student : Person
{
public ICollection<CourseBook> BooksOnLoan { get; set; }
}
public sealed class Building
{
public int BuildingId { get; set; }
public string Name { get; set; }
public Teacher AssignedGuardian { get; set; }
public int GuardianId { get; set; }
}
public sealed class CourseBook
{
public int CourseBookId { get; set; }
public int BookNumber { get; set; }
public Student AssignedTo { get; set; }
public int? AssignedToId { get; set; }
}
EntityMappings.cs does not include Building or CourseBook mapping types as they are not relevant
public sealed class SchoolMap : EntityTypeConfiguration<School>
{
public SchoolMap()
{
ToTable("Schools");
HasKey(x => x.SchoolId);
Property(x => x.SchoolId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(x => x.Name).IsRequired().IsUnicode(true).HasMaxLength(200);
HasMany(x => x.EntitiesOfSchool).WithRequired(x => x.PrimarySchool).HasForeignKey(person => person.SchoolId);
}
}
public sealed class PersonMap : EntityTypeConfiguration<Person>
{
public PersonMap()
{
ToTable("Persons");
HasKey(x => x.PersonId);
Property(x => x.PersonId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(x => x.Name).IsRequired().IsUnicode(true).HasMaxLength(256);
Map<Teacher>(configuration => configuration.Requires("PersonType").HasValue(1));
Map<Student>(configuration => configuration.Requires("PersonType").HasValue(2));
}
}
public sealed class TeacherMap : EntityTypeConfiguration<Teacher>
{
public TeacherMap()
{
HasMany(x => x.Buildings).WithRequired(x => x.AssignedGuardian).HasForeignKey(x => x.GuardianId);
}
}
public sealed class StudentMap : EntityTypeConfiguration<Student>
{
public StudentMap()
{
HasMany(x => x.BooksOnLoan).WithOptional(x => x.AssignedTo).HasForeignKey(x => x.AssignedToId);
}
}
UniversityDbContext.cs
public sealed class UniversityDbContext : DbContext
{
public UniversityDbContext() : base("Name=default")
{
this.Configuration.LazyLoadingEnabled = false; // disable lazy loading
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new SchoolMap());
modelBuilder.Configurations.Add(new PersonMap());
modelBuilder.Configurations.Add(new TeacherMap());
modelBuilder.Configurations.Add(new StudentMap());
}
public DbSet<School> Schools { get; set; }
}
Sql code for table DDE and seed of data.sql
CREATE TABLE [dbo].[Schools](
[SchoolId] [int] IDENTITY(1,1) NOT NULL,
[Name] Nvarchar (200) not null
CONSTRAINT [PK_Schools] PRIMARY KEY CLUSTERED
(
[SchoolId] ASC
)) ON [PRIMARY]
GO
CREATE TABLE [dbo].[Persons](
[PersonId] [int] IDENTITY(1,1) NOT NULL,
[SchoolId] [int] not null,
[PersonType] [int] not null,
[Name] nvarchar(256) not null
CONSTRAINT [PK_Persons] PRIMARY KEY CLUSTERED
(
[PersonId] ASC
)) ON [PRIMARY]
GO
CREATE TABLE [dbo].[Buildings](
[BuildingId] [int] IDENTITY(1,1) NOT NULL,
[Name] nvarchar(200) not null,
[GuardianId] [int] not null,
CONSTRAINT [PK_Buildings] PRIMARY KEY CLUSTERED
(
[BuildingId] ASC
)) ON [PRIMARY]
GO
CREATE TABLE [dbo].[CourseBooks](
[CourseBookId] [int] IDENTITY(1,1) NOT NULL,
[BookNumber] varchar(200) not null,
[AssignedToId] [int] null,
CONSTRAINT [PK_CourseBooks] PRIMARY KEY CLUSTERED
(
[CourseBookId] ASC
)) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Persons] WITH CHECK ADD CONSTRAINT [FK_Schools_Persons] FOREIGN KEY([SchoolId])
REFERENCES [dbo].[Schools] ([SchoolId])
ALTER TABLE [dbo].[Buildings] WITH CHECK ADD CONSTRAINT [FK_Persons_Buildings] FOREIGN KEY([GuardianId])
REFERENCES [dbo].[Persons] ([PersonId])
ALTER TABLE [dbo].[CourseBooks] WITH CHECK ADD CONSTRAINT [FK_Persons_CourseBooks] FOREIGN KEY([AssignedToId])
REFERENCES [dbo].[Persons] ([PersonId])
INSERT INTO Schools (Name) values (N'Business'), (N'Education')
INSERT INTO Persons (Name, PersonType, SchoolId) values ('Eddy',1, 1), ('Fran',1, 1), ('Joe',2, 1), ('Kim',2, 1)
INSERT INTO Buildings (Name, GuardianId) values (N'Offsite staff', 1), (N'Main Business Building', 1)
INSERT INTO CourseBooks(AssignedToId, BookNumber) values (3, 'Course A book 1'),(3, 'Course C book 31')
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.
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).