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)
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.
I have a library application and am trying to go with a code first approach which has a Book entity like this:
public class Book
{
public int Id { get; set; }
public string Code { get; set; }
public string Title { get; set; }
public string Publisher { get; set; }
public Category? Category { get; set; }
}
public enum Category : byte
{
[Description("LA_Category_Thriller")]
Thriller,
[Description("LA_Category_Adventure")]
Adventure,
[Description("LA_Category_Comic")]
Comic,
[Description("LA_Category_Mistery")]
Mistery
}
Now I am coding a BookConfiguration class and I am not sure if I am doing stuff right; I am not sure what is the best solution, to convert to string, or to also enter it in DB like SQL-enum (not sure if even possible in EF Core):
public class BookConfiguration : IEntityTypeConfiguration<Book>
{
public void Configure(EntityTypeBuilder<Book> builder)
{
builder.ToTable(nameof(Book), "lib");
builder.HasKey(b => b.Id);
builder.Property(b => b.Id).ValueGeneratedOnAdd();
builder.Property(b => b.Category)
.HasMaxLength(32)
.HasConversion<string>();
}
}
As far as I got stuff this is goint to convert it to string upon updating an entry, but will this make stuff harder later when I retrieve values from db?
I am currently learning MVC in C#, and faced with below problems.
I have 2 models created already, and both work fine.
Model 1:
public class MeetingResolutionViewModel
{
public string ResolutionNo { get; set; }
public string ResolutionCd { get; set; }
public string ResolutionDesc { get; set; }
public long EventId{ get; set; }
}
Model 1 is mapped like this:
public static void MapToMeetingResolution(IMapperConfigurationExpression cfg)
{
cfg.CreateMap<PxVotingResolution, MeetingResolutionViewModel>()
.ForMember(dest => dest.ResolutionId, m => m.MapFrom(src => src.Id))
.ReverseMap();
cfg.CreateMap<PxVotingElection, MeetingResolutionViewModel>()
.ReverseMap();
}
Model 2:
public class ProxyVotingViewModel
{
public bool RoleUpdate { get; set; }
public bool RoleElect { get; set; }
public long EventId { get; set; }
public DateTime? LastUpdatedAt { get; set; }
}
Model 2 is mapped like this:
public static void MapToProxyMeeting(IMapperConfigurationExpression cfg)
{
cfg.CreateMap<EventDetail, ProxyVotingViewModel>();
}
Model 1 and Model 2 share the same EventId column.
I am creating another view model like below. It is a combination of above 2 models with the "MeetingResolutionViewModel" model in a list.
public class ProxyVotingEmailViewModel
{
public List<MeetingResolutionViewModel> MeetingResolutionViewModel { get; set; }
public ProxyVotingViewModel ProxyVotingViewModel { get; set; }
}
Using auto mapper, I have done the mapping as following:
public static void MapToProxyVotingEmailViewModel(IMapperConfigurationExpression cfg)
{
cfg.CreateMap<ProxyVotingViewModel, ProxyVotingEmailViewModel>();
cfg.CreateMap<MeetingResolutionViewModel, ProxyVotingEmailViewModel>();
}
In my function below, how can I solve the 2 problems mentioned in the comment?
public async Task<CommandResult<List<string>>> SendEmail(List<MeetingResolutionViewModel> data)
{
//blah blah
var emailData = new ProxyVotingEmailViewModel();
var pxData = new ProxyVotingViewModel();
/* I would like to do 2 things:
* 1) get pxData using the EventId from data[0];
* 2) get emailData value by combining pxData with data (as per the view model definition)
**/
//blah blah
}
I have two objects, I want to map them using AutoMapper Attributes, these are my target objects:
public class ClaseB
{
public string UBLVersionID_nuevo { get; set; }
public ClaseB_inside objetoB_inside { get; set; }
}
public class ClaseB_inside
{
public string texto_inside { get; set; }
}
and this is my source class:
[MapsTo(typeof(ClaseB))]
public class ClaseA
{
[MapsToProperty(typeof(ClaseB), "objetoB_inside.texto_inside")]
public string texto { get; set; } = "texto prueba";
[MapsToProperty(typeof(ClaseB), "UBLVersionID_nuevo")]
public string texto2 { get; set; } = "texto 2 de prueba";
}
when I try to map I get the following error:
Error mapping types
and with this change:
[MapsTo(typeof(ClaseB))]
public class ClaseA
{
[MapsToProperty(typeof(ClaseB_inside), "objetoB_inside.texto_inside")]
public string texto { get; set; } = "texto prueba";
[MapsToProperty(typeof(ClaseB), "UBLVersionID_nuevo")]
public string texto2 { get; set; } = "texto 2 de prueba";
}
I get null in ClaseB.objetoB_inside but ClaseB.UBLVersionID_nuevo it works.
What am I doing wrong?
I think the issue is with the way you are defining the mapping. Consider the following if you weren't using Automapper attributes and was initializing through the static API:
Mapper.Initialize(expression =>
{
expression.CreateMap<ClaseA, ClaseB>()
.ForMember(
from => from.objetoB_inside.texto_inside,
to => to.MapFrom(a => a.texto2));
});
This mapping would result in the following exception:
Expression 'from => from.objetoB_inside.texto_inside' must resolve to top-level member and not any child object's properties. Use a custom resolver on the child type or the AfterMap option instead.
And I think that's the same issue with the Attributes definition.
So I would suggest implementing the following:
public class MapsToClaseB : MapsToAttribute
{
public MapsToClaseB() : base(typeof(ClaseB)) { }
public void ConfigureMapping(IMappingExpression<ClaseA, ClaseB> mappingExpression)
{
mappingExpression.AfterMap(
(a, b) => b.objetoB_inside = new ClaseB_inside{texto_inside = a.texto});
}
}
You just then need to decorate your class with this:
[MapsToClaseB]
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?