I just started learning C# and Nhibernate. I'm trying to solve following problem for hours.
Can anyone see the problem?
Table Line:
CREATE TABLE [dbo].[Line]
(
[ID] UNIQUEIDENTIFIER DEFAULT (newid()) NOT NULL,
[Name] NVARCHAR (45) NOT NULL,
[ID_Color] UNIQUEIDENTIFIER NULL,
PRIMARY KEY CLUSTERED ([ID] ASC),
UNIQUE NONCLUSTERED ([Name] ASC),
FOREIGN KEY ([ID_Color])
REFERENCES [dbo].[Line_Color] ([ID]) ON DELETE SET NULL
);
Table Line_Color:
CREATE TABLE [dbo].[Line_Color]
(
[ID] UNIQUEIDENTIFIER DEFAULT (newid()) NOT NULL,
[Color] NVARCHAR (45) NOT NULL,
PRIMARY KEY CLUSTERED ([ID] ASC),
UNIQUE NONCLUSTERED ([Color] ASC)
);
Entity.cs:
public class Entity<T> where T : Entity<T>
{
public virtual Guid Id { get; set; }
private int? _oldHashCode;
public override Boolean Equals(object obj)
{
var other = obj as T;
if (other == null)
return false;
// handle the case of comparing two NEW objects
var otherIsTransient = Equals(other.Id, Guid.Empty);
var thisIsTransient = Equals(Id, Guid.Empty);
if (otherIsTransient && thisIsTransient)
return ReferenceEquals(other, this);
return other.Id.Equals(Id);
}
public override Int32 GetHashCode()
{
if (_oldHashCode.HasValue)
return _oldHashCode.Value;
var thisIsTransient = Equals(Id, Guid.Empty);
if (thisIsTransient)
{
_oldHashCode = base.GetHashCode();
return _oldHashCode.Value;
}
return Id.GetHashCode();
}
public static Boolean operator ==(Entity<T> x, Entity<T> y)
{
return Equals(x, y);
}
public static Boolean operator !=(Entity<T> x, Entity<T> y)
{
return !(x == y);
}
}
Line.cs:
public class Line : Entity<Line>
{
public virtual String Name { get; set; }
public virtual Color Color { get; set; }
}
LineMap.cs:
public class LineMap : ClassMap<Line>
{
public LineMap()
{
Id(x => x.Id);
Map(x => x.Name);
References(x => x.Color);
}
}
Color.cs:
public class Color : Entity<Color>
{
public virtual String ColorS { get; set; }
}
ColorMap.cs:
public class ColorMap : ClassMap<Color>
{
public ColorMap()
{
Id(x => x.Id);
Map(x => x.ColorS);
}
}
On every query I do, I get something like
An unhandled exception of type
'NHibernate.Exceptions.GenericADOException' occurred in NHibernate.dll
Additional information: could not execute query
or NULL.
There are 2 problems in your code:
An entity is named Color and the corresponding table Line_Color;
A property is named ColorS and the corresponding column Color.
They must correspond, or you have to tell NHibernate the DB names. Do some renaming (preferably) or adjust your mapping.
ColorMap.cs:
public class ColorMap : ClassMap<Color>
{
public ColorMap()
{
Table("Line_Color");
Id(x => x.Id);
Map(x => x.ColorS).Column("Color");
}
}
Entities represents tables (or table likes).
You don't need an table (and entity/map) for "color".
You don't need to have a class for "Color".
Line need to have a property and a map for property "color".
The problem Hibernate acuses is because it can't match the Entity color to any table.
Related
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")
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')
Suppose I have following table:
public class ResourcePossibleUm : EntityBase
{
public virtual bool IsMain { get; set; }
public virtual double UmMass { get; set; }
-------
}
And mapping:
public class ResourcePosibleUMMap : ClassMap<ResourcePossibleUm>
{
public ResourcePosibleUMMap()
{
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.IsMain);
Map(c => c.UmMass);
}
}
I am working with sql server 2008.
By default sql server 2008 will set my primary key (Id) like clustered index but I really don't want it . I want ["IsMain"] column to be clustered index.I want to set ["IsMain"] column like clustered index s using fluent Nhibernate but I can't figure out how to do this. Thanks for your attention!
SOLUTION:
public class CustomMsSql2008Dialect : MsSql2008Dialect
{
public override string PrimaryKeyString
{
get { return "primary key nonclustered"; }
}
}
Now using CustomMsSql2008Dialect like Dialect in configuration initialization I will not have primary key like clustered index , id will be nonclustered index.
I have these classes:
public class Entity
{
public virtual Guid Id { get; set; }
public virtual string EntityName { get; set; }
public virtual IDictionary<string, Property> { get; set; }
// ...
}
public class Property
{
public virtual int? IntValue { get; set; }
public virtual decimal? DecimalValue { get; set; }
}
Is it possible to create Fluent NHibernate mappings so that executing the resulting schema will give these tables:
[Entities]
* Id : UNIQUEIDENTIFIER NOT NULL
* EntityName : NVARCHAR(50) NOT NULL
with a clustered index on "Id"
[Properties]
* EntityId : UNIQUEIDENTIFIER NOT NULL
* PropertyName : VARCHAR(50) NOT NULL
* IntValue : INT NULL
* DecimalValue : DECIMAL(12,6) NULL
with a clustered index on "EntityId" and "PropertyName"
Or do I need to change my classes?
An answer more verbose than yes/no will be much appreciated :)
Besides your clustered index which you would need to manually create, yes. Do you absolutely require FNH to generate your schema?
Why don't you just generate the schema yourself specific to your requirements and then map it accordingly.
(not tested or anything, written off the top of my head)
public class EntityMap : ClassMap<Entity>
{
public EntityMap()
{
Table("Entities");
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Name).CustomSqlType("NVARCHAR").Length(50).Not.Nullable();
HasMany<Property>(x => x.Properties)
.Table("Properties")
.KeyColumn("PropertyName")
.Inverse()
.AsBag();
}
}
public class PropertyMap : ClassMap<Property>
{
public PropertyMap()
{
Table("Properties");
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.PropertyName).Length(50).Not.Nullable();
Map(x => x.IntValue);
Map(x => x.DecimalValue);
}
}
Here's the mapping that I have come up with:
public sealed class EntityMap : ClassMap<Entity>
{
public EntityMap()
{
Table("Entities");
Id(c => c.Id);
Map(c => c.EntityName).CustomSqlType("nvarchar(50)").Not.Nullable();
HasMany(c => c.Properties)
.KeyColumn("EntityId")
.AsMap<string>("PropertyName")
.Component(part =>
{
part.Map(x => x.IntValue);
part.Map(x => x.DecimalValue).Precision(12).Scale(6);
});
}
}
Schema generation yields this:
create table Entities (
Id UNIQUEIDENTIFIER not null,
EntityName nvarchar(50) not null,
primary key (Id)
)
create table Properties (
EntityId UNIQUEIDENTIFIER not null,
IntValue INT null,
DecimalValue DECIMAL(12, 6) null,
PropertyName INT not null,
primary key (EntityId, PropertyName)
)
alter table Properties
add constraint FK63646D8550C14DC4
foreign key (EntityId)
references Entities
Which is pretty much what I need, with an exception of the column order (minor issue) and PropertyName being nvarchar(255) instead of varchar(50) (something I actually care about).
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.