Problem seems pretty generic, but I am new to fluentnhibernate and all my searches did not turn up anything useful.
I have a class hierarchy similar to this one:
public class Person
{
public virtual int Id { get; protected set; }
public virtual string GivenName { get; set; }
public virtual string FamilyName { get; set; }
}
public class Employee : Person
{
public virtual string WhoAmI { get { return "I am just an Employee"; } protected set { } }
}
public class Boss : Employee
{
public override string WhoAmI { get { return "I am the boss."; } protected set { } }
}
I.e. one base class and two subclasses (one derived from the other). The subclasses have the same signature, but the base class has not.
The mapping below will put all information about the instances of the three classes into the same table:
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Id(x => x.Id);
Map(x => x.GivenName);
Map(x => x.FamilyName);
DiscriminateSubClassesOnColumn("type");
}
}
public class EmployeeMap : SubclassMap<Employee>
{
public EmployeeMap()
{
Map(x => x.WhoAmI);
}
}
public class BossMap : SubclassMap<Boss>
{
public BossMap()
{
}
}
Leaving out the DiscriminateSubClassesOnColumn will generate three tables:
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Id(x => x.Id);
Map(x => x.GivenName);
Map(x => x.FamilyName);
}
}
public class EmployeeMap : SubclassMap<Employee>
{
public EmployeeMap()
{
Map(x => x.WhoAmI);
}
}
public class BossMap : SubclassMap<Boss>
{
public BossMap()
{
}
}
My question: Is it possible (in fluentnhibernate) to map these classes into exactly two tables, i.e. to have
one Person table that holds all the properties derived from the Person class, and
an Employee table that holds all the properties derived from the Employee class, plus a discriminator that discriminates between normal Employees and Bosses?
If you simply alias BossMap to EmployeeMap:
public class BossMap : EmployeeMap {}
you should get the mapping that you need.
Related
I am using DDD to implement a domain and using EF Core and SQL as infrastructure for persistence. I have a model which is implemented by "Composite Design Pattern". I use fake name that does not show actual model but consider there is a School entity that has many Rooms:
public class Person: AggregateRoot<long>
{
public long Id { get; set; }
public string Name { get; set; }
private List<Document> _documents;
public IReadOnlyList<Document> Documents=> _documents.AsReadOnly();
}
Document is an abstract class that can be PersonDocument or CategoryDocument:
public abstract class Document: ValueObject
{
public string Title { get; set; }
}
and:
public class PersonDocument : Document
{
public string Code { get; set; }
}
public class CategoryDocument: Document
{
private List<Document> _documents;
public IReadOnlyList<Document> Documents=> _documents.AsReadOnly();
}
Three tables will be generated in SQL. Here is my mapping:
public override void EntityTypeConfiguration(EntityTypeBuilder<Person> builder)
{
builder.ToTable("Persons");
builder.HasKey(w => w.Id);
builder.Property(w => w.Id)
.ValueGeneratedNever();
builder.Property(x => x.Name).IsRequired().HasMaxLength(500);
}
public class DocumentMapping : IEntityTypeConfiguration<Document>
{
public void Configure(EntityTypeBuilder<Document> builder)
{
builder.Property<long>("Id").ValueGeneratedOnAdd();
builder.ToTable("Documents").HasKey("Id");
builder.UsePropertyAccessMode(PropertyAccessMode.Field);
builder.Property(x => x.Title).IsRequired().HasMaxLength(500);
builder.HasOne<Person>().WithMany(x => x.Documents).HasForeignKey("PersonId");
}
}
public class PersonDocumentMapping : IEntityTypeConfiguration<PersonDocument>
{
public void Configure(EntityTypeBuilder<PersonDocument> builder)
{
builder.Property<long>("Id").ValueGeneratedOnAdd();
builder.ToTable("PersonDocuments");
//builder.HasBaseType<Document>();
builder.Property(x => x.Code).IsRequired().HasMaxLength(500);
}
}
public class CategoryDocumentMapping : IEntityTypeConfiguration<CategoryDocument>
{
public void Configure(EntityTypeBuilder<CategoryDocument> builder)
{
builder.Property<long>("Id").ValueGeneratedOnAdd();
builder.ToTable("Categories");
// builder.HasBaseType<Document>();
builder.HasMany<Document>(x => x.Documents).WithOne().HasForeignKey("CategoryId");
}
}
result of mapping is this:
Tables and relations in database is as I expect. But when I try to fetch a person from database, desired person get fetched but its documents don't.
namespace Test
{
public class A
{
public string Name { get; set; }
}
public class AValidator : AbstractValidator<A>
{
public AValidator()
{
RuleFor(t => t.Name)
.NotEmpty()
.MinimumLength(10)
.MaximumLength(20);
}
}
public class B
{
public string Name { get; set; }
}
public class BValidator : AbstractValidator<B>
{
public BValidator()
{
RuleFor(t => t.Name)
.NotEmpty()
.MinimumLength(10)
.MaximumLength(20);
}
}
}
tried to create a common like this:
namespace Test2
{
public interface IPerson
{
public string Name { get; set; }
}
public abstract class CommonABValidators<T> :
AbstractValidator<T> where T : IPerson
{
protected CommonABValidators()
{
RuleFor(x => x.Name).NotNull();
}
}
}
but by calling
public class AValidator : CommonABValidators<A>
It should be convertible to IPerson, but in my case I have diffeent props in A that are not convertible to IPerson
Any Idea how to extract common params into common Validator?
What you've done looks correct. Just make sure that your class A implements the IPerson interface and the validator will start working.
Another option that you can try at least is to Include rules. Here you can find the official documentation: https://docs.fluentvalidation.net/en/latest/including-rules.html
All you need is to call Include(new CommonValidator()); and the common rules will be automatically included without inheriting a common base type.
I have this exception "illegal attempt to associate a collection with two open sessions", it raises every time I save entity contains collection of children.
I google it. I found that I opened two or more sessions when calling save, but I'm sure that I'm using only one session.
Where I did wrong? How can I solve this problemn?
Note: I'm using MVC4, and fluent NHibernate.
Entities:
public class Employee : EntityBase<int>
{
public Employee()
: base()
{
Phones = new List<Phone>();
}
public Employee(int id) : this() { Id = id; }
[Browsable(false)]
public override ApprovalBase Approval
{
get;
set;
}
public virtual string Name { get; set; }
public virtual string Job { get; set; }
[Browsable(false)]
public virtual IList<Phone> Phones { get; set; }
}
public class Phone : EntityBase<int>
{
public Phone()
: base()
{
}
public Phone(int id) : this() { Id = id; }
public override ApprovalBase Approval
{
get;
set;
}
public virtual string PhoneNumber { get; set; }
public virtual string PhoneType { get; set; }
public virtual int EmployeeId { get; set; }
public virtual Employee Employee { get; set; }
}
Mapping:
public sealed class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
Table("dbo.Employee");
Id(x => x.Id).Column("EmployeeId");
Map(x => x.Name);
Map(x => x.Job);
HasMany(x => x.Phones).KeyColumn("EmployeeId").Table("dbo.Phone").Cascade.All().Inverse();
}
}
public sealed class PhoneMap : ClassMap<Phone>
{
public PhoneMap()
{
Table("dbo.Phone");
Id(x => x.Id).Column("PhoneId");
Map(x => x.PhoneNumber);
Map(x => x.PhoneType);
Map(x => x.EmployeeId);
References(x => x.Employee).Column("EmployeeId")
.Not.Update()
.Not.Insert();
}
}
Repository:
public abstract class RepositoryBase<TEntity, TIdentity>
: IRepository<TEntity, TIdentity>
where TEntity : EntityBase<TIdentity>
where TIdentity : IComparable
{
private readonly IPersistor<TEntity, TIdentity> persistor; //contains the session to operate with the database
public IPersistor<TEntity, TIdentity> Persistor { get { return persistor; } }
private readonly IFinder<TEntity, TIdentity> finder;
public IFinder<TEntity, TIdentity> Finder { get { return finder; } }
private RepositoryBase() { }
public RepositoryBase(
IPersistor<TEntity, TIdentity> persistor,
IFinder<TEntity, TIdentity> finder)
{
this.persistor = persistor;
this.finder = finder;
this.finder.DataSource = Query();
}
// Get entity by ID
public virtual TEntity Get(TIdentity id)
{
return persistor.Get(id);
}
/// <summary>
/// Validate and Save the entity. If the validation failed, will not save the entity,
/// but returns back list of error messages.
/// </summary>
public virtual IList<String> Save(TEntity entity)
{
if (entity == null)
throw new ArgumentNullException("entity");
IList<String> errors = entity.Validate();
if (errors.Count == 0)
{
persistor.Save(entity);
}
return errors;
}
// Delete entity from persistance repository
public virtual void Delete(TIdentity id)
{
persistor.Delete(id);
}
/// Gets IQueryable which we use from the concrete
/// implementation of repository to implement our
/// query methods (FindBy).
protected IQueryable<TEntity> Query()
{
return persistor.Query();
}
public IList<TEntity> GetAll()
{
return persistor.Query().ToList();
}
}
public class EmployeeRepository : RepositoryBase<Employee, int>, IEmployeeRepository
{
public EmployeeRepository(
IPersistor<Employee, int> persistor,
IEmployeeFinder entityFinder)
: base(persistor, entityFinder) { }
public IEmployeeFinder Find
{
get { return (IEmployeeFinder)Finder; }
}
}
public class PhoneRepository : RepositoryBase<Phone, int>, IPhoneRepository
{
public PhoneRepository(
IPersistor<Phone, int> persistor,
IPhoneFinder entityFinder)
: base(persistor, entityFinder) { }
public IPhoneFinder Find
{
get { return (IPhoneFinder)Finder; }
}
}
After I fill all the information of the Employee and add collection of phones, when I press save, the information haven't been saved in the database. After some debugging, I found that when my program reaches to "Session.SaveOrUpdate(entity);" the exception above appeared.
How to solve this issue?
Also, for completeness, there are usually two types of issues:
Commonly related to improperly managed DAO objects with their own ISession creation:
As in the example defined above, there could be two objects in play to work with the repository: (a) persistor and (b) finder.
They each have an instance of ISession. If finder succeeds and finds something (e.g. Phones), the entity Employee asking for them tries to Save, but Phones are inside a different session than Employee.
Very often related to ASP.NET MVC and its Redirect() action results:
Most likely Phones were loaded by a different session. Not by the "current" that processes the Employee.
So the most suspect is the call Redirect(). If yes, what happens is that we load an object in one controller life time - put it into Temp dictionary - call Redirect to other controller - and now there is a new session as well as an older object associated with an older, closed session.
Solution: Be sure, that all the DAO handling is part of one ISession scope. Do not transfer any data among sessions (nor among controller redirections)...
I solved it with lock statements in every methods where there was static variables being used, because the root of the problem was syncronization related.
Here is a simple example to ilustrate my solution:
private static int sharedVariable;
private static object _syncronizationObject = new Object();
public void MethodThatUsesStaticVariable(int newValue)
{
// This lock prevents concurrency problems, and this is what solved the issue for me.
lock(_syncronizationObject)
{
sharedVariable = newValue;
}
}
I want to mapping following simple entities:
public class AnimalsOwner
{
public AnimalsOwner()
{
Cats = new List<Cat>();
Dogs = new List<Dog>();
}
public virtual int Id { get; set; }
public virtual IList<Cat> Cats { get; set; }
public virtual IList<Dog> Dogs { get; set; }
}
public abstract class Animal
{
public virtual int Id { get; set; }
public virtual AnimalsOwner AnimalsOwner { get; set; }
}
public class Dog : Animal
{
public virtual string DogsProperty { get; set; }
}
public class Cat : Animal
{
public virtual string CatsProperty { get; set; }
}
With the following mappings:
public OwnerMapping()
{
this.Table("Owners");
Id(x => x.Id)
.GeneratedBy.Native();
HasMany(x => x.Cats).AsBag().Cascade.SaveUpdate();
HasMany(x => x.Dogs).AsBag().Cascade.SaveUpdate();
}
}
public class AnimalMapping : ClassMap<Animal>
{
public AnimalMapping()
{
this.Table("Animals");
Id(x => x.Id).GeneratedBy.Native();
References(x => x.AnimalsOwner).Not.Nullable();
}
}
public class DogMap : SubclassMap<Dog>
{
public DogMap()
{
this.Table("Dogs");
this.KeyColumn("AnimalId");
Map(x => x.DogsProperty);
}
}
public class CatMap : SubclassMap<Cat>
{
public CatMap()
{
this.Table("Cats");
this.KeyColumn("AnimalId");
Map(x => x.CatsProperty);
}
}
With such mappings everything works fine, but generated schema looks like following:
And such code:
var owner = new AnimalsOwner();
owner.Cats.Add(new Cat()
{
AnimalsOwner = owner,
CatsProperty = "cat"
});
Makes inserts in all three tables (Animals, Cats, Dogs) of column OwnderId. But isn't it enough to have it only in Animals table? If yes then how to achieve it? If NHibernate acts as expected then why is he doing such a duplication?
I would map this using table-per-class strategy and filter the public collections by type. This database structure is much easier to work with and more efficient to query.
public class AnimalMapping : ClassMap<Animal>
{
public AnimalMapping()
{
Table("Animals");
Id(x => x.Id).GeneratedBy.Native();
References(x => x.AnimalsOwner);
DiscriminateSubClassesOnColumn("AnimalType");
}
}
public class DogMap : SubclassMap<Dog>
{
public DogMap()
{
DiscriminatorValue("DOG");
Map(x => x.DogsProperty);
}
}
public class CatMap : SubclassMap<Cat>
{
public CatMap()
{
DiscriminatorValue("CAT");
Map(x => x.CatsProperty);
}
}
The Dogs and Cats collection can be exposed by mapping the Animals collection as a private member and filtering by type:
public class AnimalsOwner
{
private IList<Animal> _animals;
public AnimalsOwner()
{
_animals = new List<Animal>();
}
public virtual int Id { get; set; }
public virtual IEnumerable<Animal> Animals { get { return _animals; } }
public virtual IEnumerable<Cat> Cats { get { return _animals.OfType<Cat>(); } }
public virtual IEnumerable<Dog> Dogs { get { return _animals.OfType<Dog>(); } }
public virtual void AddAnimal(Animal animal)
{
if (!_animals.Contains(animal))
{
animal.AnimalsOwner = this;
_animals.Add(animal);
}
}
public virtual void RemoveAnimal(Animal animal)
{
if (_animals.Contains(animal))
{
animal.AnimalsOwner = null;
_animals.Remove(animal);
|
}
}
The mapping for this class would be:
public OwnerMapping()
{
this.Table("Owners");
Id(x => x.Id).GeneratedBy.Native();
HasMany(x => x.Animals.AsBag.Cascade.AllDeleteOrphan()
.Inverse().LazyLoad()
.Access.CamelCaseField(Prefix.Underscore);
}
}
And the code for adding a cat would be:
var owner = new AnimalsOwner();
owner.AddAnimal(new Cat()
{
CatsProperty = "cat"
});
You're getting the duplicates becuse you have HasMany Cats and Dogs defined in the owner mapping (and Cat & Dog collections in the Owner class). Perhaps what you really want is a collection of Animals in the owners class (not individually dogs and cats) and just one HasMany Animals in the Owner mapping?
I'm using fluent Nhibernate to map a simple class
And using Schema Generation to create this class on MySQL DB.
I can't use IList<> for my properties (I'm mapping cross-language domain classes)
So I have to use simple arrays..
I Want NHibernate to create a connection table between the two classes,
These are the domain classes:
public class ClassOne
{
public virtual Guid Guid { get; set; }
public virtual String Title { get; set; }
public virtual ClassTwo[] Tags { get; set; }
}
public class ClassTwo
{
public virtual Guid Guid { get; set; }
public virtual string Title { get; set; }
}
And this is the map:
public class ClassOneMap : ClassMap<ClassOneMap>
{
public ClassOneMap ()
{
Id(x => x.Guid).GeneratedBy.GuidComb();
Map(x => x.Title);
HasManyToMany(x => x.Tags)
.Cascade.SaveUpdate());
}
}
public class ClassTwoMap : ClassMap<ClassTwo>
{
public ClassTwoMap()
{
Id(x => x.Guid).GeneratedBy.GuidComb();
Map(x => x.Title);
}
}
The schema generates great! It has a ClassOne, ClassTwo and ClassTwoToClassOne Tables
But when I'm trying to persist an instance of ClassOne I have an Invalid Cast exception..
This is solved by changing the arrays to IList's but I can't really do that..
Can anyone tell me how to configure the Fluent mapping to use Arrays without changing the schema architecture?
Thanks A'lot!
Ok, played around this and hope that solve the question.
So models are:
public class ClassOne : Entity
{
public virtual string Title { get; set; }
public virtual ClassTwo[] Tags { get; set; }
}
public class ClassTwo : Entity
{
public virtual string Title { get; set; }
}
Base class contains the Id definition which is long in my case. Should not be a problem with Guids
Mapping class: We are using FluentNhibernate with some convention, also the idea is in HasManyToMany
public class ClassOneMappingOverride : IAutoMappingOverride<ClassOne>
{
public void Override(AutoMapping<ClassOne> mapping)
{
mapping.HasManyToMany(x => x.Tags).AsArray(x => x.Id).ParentKeyColumn("classOneId")
.ChildKeyColumn("classTwoId")
.Table("ClassOneLinkClassTwo")
.Cascade.SaveUpdate();
}
}
Please note that if you not indicate ParentKey, ChildKey and Table it will not create the link table.
The unit test which insert data looks like:
public class ClassOneDataPart : DataPartBase, IDataPart
{
public void AddToDatabase()
{
var classOne = new ClassOne { Title = "classOne" };
var classTwo1 = new ClassTwo { Title = "class21" };
var classTwo2 = new ClassTwo { Title = "class22" };
var tags = new[] { classTwo1, classTwo2 };
classOne.Tags = tags;
this.SaveData(classOne);
this.SaveData(classTwo1);
this.SaveData(classTwo2);
}
}
and the result into database is:
Regards,
Ion
Map the collection as a private field and expose it as an array. This also makes it easy to expose AddTag and RemoveTag methods without manipulating the array.
public class ClassOne
{
private IList<ClassTwo> _tags;
public virtual Guid Guid { get; set; }
public virtual String Title { get; set; }
public virtual ClassTwo[] Tags
{
// possibly expose as method to hint that the array is re-built on every call
get { return _tags.ToArray(); }
}
}
public class ClassOneMap : ClassMap<ClassOne>
{
public ClassOneMap ()
{
Id(x => x.Guid).GeneratedBy.GuidComb();
Map(x => x.Title);
HasManyToMany(x => x.Tags).Access.CamelCaseField(Prefix.Underscore)
.Cascade.SaveUpdate());
}
}
Try to use .AsArray(x=>x.Id)