Insert nested object via JSON using web api with NHibernate - .net-core

I'm writing an application that consist of a ReadAPI and a WriteAPI. The read API contains the domain classes, and uses EF CORE code first to generate the SQL DB, and to read from it.
The write API uses NHibernate to write to the database that is generated by EF Core. So far I have inserted 'simple' object via the write API, which has worked fine.
I'm encountering a problem now. I have a domain class, Driver, that has a nested object, Address, inside. At DB level a driver can have one address and an address can belong to multiple drivers. I'm trying to POST a JSON object, a driver object, via the write API. As to now, I've solved inserting the address by creating an address record in DB in advance, and giving the address ID in the JSON.
What I want to do now is giving the complete nested JSON object and have NHibernate generate the inserts for me. I have tried so many things but I feel like I am getting nowhere. Any advice would be much appreciated.
I'm using .Net Core with NHiberate 5.3.5, which has the mapping by code functionality already. If someone can solve it using Fluent NH instead, that is fine as well since I will convert it to NH 5.3.5 notation myself then.
My code:
Domain classes:
Driver:
namespace Models
{
public class Chauffeur : IIdentifiable
{
public virtual long Id { get; set; }
public virtual string Naam { get; set; }
public virtual string Voornaam { get; set; }
public virtual DateTime GeboorteDatum { get; set; }
//todo validatie
public virtual string RijksRegisterNummer { get; set; }
public virtual RijbewijsTypes TypeRijbewijs { get; set; }
public virtual bool Actief { get; set; }
//rel adres
public virtual long AdresId { get; set; }
public virtual Adres Adres { get; set; }
//rel tankkaart
public virtual long TankkaartId { get; set; }
public virtual Tankkaart Tankkaart { get; set; }
}
}
Address:
namespace Models
{
public class Adres : IIdentifiable
{
public virtual long Id { get; set; }
public virtual string Straat { get; set; }
public virtual int Nummer { get; set; }
public virtual string Stad { get; set; }
public virtual int Postcode { get; set; }
public virtual ICollection<Chauffeur> Chauffeurs { get; set; }
}
}
My DriverMap so far:
namespace WriteAPI
{
public class ChauffeurMap : ClassMapping<Chauffeur>
{
public ChauffeurMap()
{
this.Table("Chauffeurs");
this.Id(c => c.Id, c =>
{
c.Generator(Generators.Native);
c.Type(NHibernateUtil.Int64);
c.Column("Id");
c.UnsavedValue(0);
});
this.Property(c => c.Naam);
this.Property(c => c.Voornaam);
this.Property(c => c.GeboorteDatum);
this.Property(c => c.RijksRegisterNummer);
this.Property(c => c.TypeRijbewijs);
this.Property(c => c.Actief);
this.Property(c => c.AdresId);
this.Property(c => c.TankkaartId);
}
}
}
Using this mapping i could insert the nested object, by using an existing child address ID.
How I inserted this via a post:
{
"Naam" : "Bart",
"Voornaam" : "Jannsses",
"AdresId" : 4,
"GeboorteDatum" : "1979-04-25",
"RijksRegisterNummer" : "999-888-7777",
"TypeRijbewijs" : 1,
"Actief" : true
}
How I would like to insert it in the future:
{
"Naam" : "Bart",
"Voornaam" : "Jannsses",
"Adres" : {
"Straat": "Boomstraat",
"Nummer": 1,
"Stad": "Gent",
"Postcode": 9000
},
"GeboorteDatum" : "1979-04-25",
"RijksRegisterNummer" : "999-888-7777",
"TypeRijbewijs" : 1,
"Actief" : true
}
Note that the ID of an address is auto generated at DB level.
Any help would be much appreciated.
Kind regards

You would need to add ManyToOne mapping in your ChauffeurMap class
this.ManyToOne(x => x.Adres , m =>
{
m.Column("AdresId");
// AdresId can be insert and update
m.Update(true);
m.Insert(true);
m.Cascade(Cascade.None);
m.Fetch(FetchKind.Join);
m.NotFound(NotFoundMode.Exception);
m.Lazy(LazyRelation.Proxy);
m.ForeignKey("AdresId");
});
And you would also need additional mapping class for Adres as AdresMap. I hope you already have it. If not, please add it as below -
public class AdresMap : ClassMapping<Adres>
{
public AdresMap()
{
this.Table("Adres"); //Your table name
this.Id(c => c.Id, c =>
{
c.Generator(Generators.Native);
c.Type(NHibernateUtil.Int64);
c.Column("Id");
c.UnsavedValue(0);
});
Set(x => x.Chauffeurs, c =>
{
c.Key(k =>
{
k.Column("Id");
k.ForeignKey("AdresId");
});
c.Inverse(true);
c.Cascade(Cascade.None);
},
r => r.OneToMany(o => { }));
this.Property(x => x.Straat );
// ... other properties
}
}

Related

Mapping SQL View in EF Core 5 - SaveChanges

I'm trying to add a view as a Navigation Property of an entity.
public class Schedule
{
public int Id { get; set; }
public decimal ScheduledQuantity { get; set; }
public ScheduleDetails ScheduleDetails { get; set; }
}
public class ScheduleDetails
{
public int ScheduleId { get; set; }
public decimal BadQuantity { get; set; }
public Schedule Schedule { get; set; }
}
with mappings:
public class ScheduleDetailMap : IEntityTypeConfiguration<ScheduleDetails>
{
public void Configure(EntityTypeBuilder<ScheduleDetails> builder)
{
builder.ToView("vwScheduleDetails", "ShopOrders");
builder.HasKey(t => t.ScheduleId);
builder.HasOne(p => p.Schedule).WithOne(s => s.ScheduleDetails);
}
}
public class ScheduleMap : IEntityTypeConfiguration<Schedule>
{
public void Configure(EntityTypeBuilder<Schedule> builder)
{
builder.ToTable("Schedules");
builder.HasKey(t => t.Id);
builder.Property(t => t.Id).UseIdentityColumn();
}
}
when I query it works fine. However if I add a new Schedule record.
var schedule = new Schedule
{
ScheduledQuantity = 100,
ScheduleDetails = new ScheduleDetails()
};
context.Schedules.Add(schedule);
context.SaveChanges();
I get an exception saying " The entity type 'ScheduleDetails' is not mapped to a table, therefore the entities cannot be persisted to the database. Use 'ToTable' in 'OnModelCreating' to map it."
Is there anyway to get EF to ignore saving this 'entity'?
This is kind of an old question, but for anyone having similar issues - in my case the problem lied in navigation properties in my view. I had some leftover properties in view's class, because its code was copied from other entity. By removing those properties, the error was gone.
This doesn't really help if you want to use navigation properties in your code, but it may help someone to continue their search.

How to fix the unmapped members?

I am building a new project for browsing through movies and giving your opinion for them. Now I am on the administration part and I added functionality for adding a movie but when I try to add a movie the automapper throws exception for unmapped members on the service where I am mapping dto to data model. The members are from the base data model for example the id.
EDIT:
I tried to ignore all the members that make this exception, also tried to made a constructor with no arguments but doesn't work.
// Initialization
Mapper.Initialize(conf =>
{
conf.CreateMap<Movie, MovieDto>();
conf.CreateMap<MovieDto, Movie>();
conf.CreateMap<MovieDto, MovieViewModel>();
});
// Base Data Model
public class DataModel
{
[Key]
public int Id { get; set; }
[DataType(DataType.DateTime)]
public DateTime? CreatedOn { get; set; }
[DataType(DataType.DateTime)]
public DateTime? ModifiedOn { get; set; }
public bool IsDeleted { get; set; }
[DataType(DataType.DateTime)]
public DateTime? DeletedOn { get; set; }
}
// Movie Data Model
public class Movie: DataModel
{
public Movie(string title, double rating, string duration, string type, string description, DateTime releaseDate, string producer)
{
this.Title = title;
this.Rating = rating;
this.Duration = duration;
this.Type = type;
this.Description = description;
this.ReleaseDate = releaseDate;
this.Producer = producer;
}
// successfully mapped members
}
// Movie DTO
public class MovieDto
{
public string Title { get; set; }
public double Rating { get; set; }
public string Duration { get; set; }
public string Type { get; set; }
public string Description { get; set; }
public DateTime ReleaseDate { get; set; }
public string Producer { get; set; }
}
// Add functionality
public void AddMovie(MovieDto movie)
{
//execption here
var movieDM = this.mapper.Map<Movie>(movie);
this.repo.Add(movieDM);
this.saver.SaveChanges();
}
This is the exception on img: https://i.imgur.com/RGZP6NP.png
Got it to work by doing the following.
Firstly, since DataModel is a base class, I followed automapper's mapping inheritance (see docs).
Then since you are using a mapper instance to map this.mapper.Map<Movie>(movie), the configuration needs to be instance rather than static as well, and I use the AutoMapper.Extensions.Microsoft.DependencyInjection nuget package for this, which allows registering Automapper with the IoC container.
My configuration looks like this (inside the ConfigureServices method of the Startup class).
services.AddAutoMapper(conf =>
{
conf.CreateMap<object, DataModel>()
.ForMember(d => d.Id, opts => opts.Ignore())
.ForMember(d => d.CreatedOn, opts => opts.MapFrom(_ => DateTime.Now))
.ForMember(d => d.ModifiedOn, opts => opts.MapFrom(_ => DateTime.Now))
.ForMember(d => d.DeletedOn, opts => opts.MapFrom(_ => (DateTime?)null))
.ForMember(d => d.IsDeleted, opts => opts.MapFrom(_ => false))
.Include<MovieDto, Movie>();
conf.CreateMap<Movie, MovieDto>();
conf.CreateMap<MovieDto, Movie>();
});
Note that I used CreateMap<object, DataModel> for the base class mapping and just used hardcoded values for dates there, feel free to adjust to suit your scenario.
After injecting an instance of IMapper, I was able to call this.mapper.Map<Movie>(movie) successfully.
Hope this sets u off in a good direction.
You can specify that AutoMapper should not validate that all properties are being mapped. The MemberList enum can be used for this when creating the mapping configuration. For example:
conf.CreateMap<MovieDto, Movie>(MemberList.None)
The error in the screenshot however indicates that another mapping is problematic, the one from MovieViewModel to MovieDto. I suggest you add a mapping configuration for these types as well:
conf.CreateMap<MovieViewModel, MovieDto>(MemberList.None)
You could try Profile Instances.
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
CreateMap<OrderViewModel, Order>()
.ForMember(dest => dest.OrderItem, opt => opt.MapFrom(src => src.OrderItemViewModel));
CreateMap<OrderItemViewModel, OrderItem>();
CreateMap<Order, Order>()
.ForMember(dest => dest.Id, opt => opt.Ignore());
CreateMap<Movie, MovieDto>();
CreateMap<MovieDto, Movie>();
}
}
Here is the working demo AutoMapperProfile

Automapper - Mapper already initialized error

I am using AutoMapper 6.2.0 in my ASP.NET MVC 5 application.
When I call my view through controller it shows all things right. But, when I refresh that view, Visual Studio shows an error:
System.InvalidOperationException: 'Mapper already initialized. You must call Initialize once per application domain/process.'
I am using AutoMapper only in one controller. Not made any configuration in any place yet nor used AutoMapper in any other service or controller.
My controller:
public class StudentsController : Controller
{
private DataContext db = new DataContext();
// GET: Students
public ActionResult Index([Form] QueryOptions queryOptions)
{
var students = db.Students.Include(s => s.Father);
AutoMapper.Mapper.Initialize(cfg =>
{
cfg.CreateMap<Student, StudentViewModel>();
});
return View(new ResulList<StudentViewModel> {
QueryOptions = queryOptions,
Model = AutoMapper.Mapper.Map<List<Student>,List<StudentViewModel>>(students.ToList())
});
}
// Other Methods are deleted for ease...
Error within controller:
My Model class:
public class Student
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string CNIC { get; set; }
public string FormNo { get; set; }
public string PreviousEducaton { get; set; }
public string DOB { get; set; }
public int AdmissionYear { get; set; }
public virtual Father Father { get; set; }
public virtual Sarparast Sarparast { get; set; }
public virtual Zamin Zamin { get; set; }
public virtual ICollection<MulaqatiMehram> MulaqatiMehram { get; set; }
public virtual ICollection<Result> Results { get; set; }
}
My ViewModel Class:
public class StudentViewModel
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string CNIC { get; set; }
public string FormNo { get; set; }
public string PreviousEducaton { get; set; }
public string DOB { get; set; }
public int AdmissionYear { get; set; }
public virtual FatherViewModel Father { get; set; }
public virtual SarparastViewModel Sarparast { get; set; }
public virtual ZaminViewModel Zamin { get; set; }
}
If you want/need to stick with the static implementation in a unit testing scenario, note that you can call AutoMapper.Mapper.Reset() before calling initialize. Do note that this should not be used in production code as noted in the documentation.
Source: AutoMapper documentation.
When you refresh the view you are creating a new instance of the StudentsController -- and therefore reinitializing your Mapper -- resulting in the error message "Mapper already initialized".
From the Getting Started Guide
Where do I configure AutoMapper?
If you're using the static Mapper method, configuration should only happen once per AppDomain. That means the best place to put the configuration code is in application startup, such as the Global.asax file for ASP.NET applications.
One way to set this up is to place all of your mapping configurations into a static method.
App_Start/AutoMapperConfig.cs:
public class AutoMapperConfig
{
public static void Initialize()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Student, StudentViewModel>();
...
});
}
}
Then call this method in the Global.asax.cs
protected void Application_Start()
{
App_Start.AutoMapperConfig.Initialize();
}
Now you can (re)use it in your controller actions.
public class StudentsController : Controller
{
public ActionResult Index(int id)
{
var query = db.Students.Where(...);
var students = AutoMapper.Mapper.Map<List<StudentViewModel>>(query.ToList());
return View(students);
}
}
I've used this method before and it worked till version 6.1.1
Mapper.Initialize(cfg => cfg.CreateMap<ContactModel, ContactModel>()
.ConstructUsing(x => new ContactModel(LoggingDelegate))
.ForMember(x => x.EntityReference, opt => opt.Ignore())
);
Since version 6.2, this doesn't work any more. To correctly use Automapper create a new Mapper and us this one like this:
var mapper = new MapperConfiguration(cfg => cfg.CreateMap<ContactModel, ContactModel>()
.ConstructUsing(x => new ContactModel(LoggingDelegate))
.ForMember(x => x.EntityReference, opt => opt.Ignore())).CreateMapper();
var model = mapper.Map<ContactModel>(this);
In case you really need to "re-initialize" AutoMapper you should switch to the instance based API to avoid System.InvalidOperationException: Mapper already initialized. You must call Initialize once per application domain/process.
For example, when you are creating the TestServer for xUnit tests you can just set ServiceCollectionExtensions.UseStaticRegistration inside fixure class constructor to false to make the trick:
public TestServerFixture()
{
ServiceCollectionExtensions.UseStaticRegistration = false; // <-- HERE
var hostBuilder = new WebHostBuilder()
.UseEnvironment("Testing")
.UseStartup<Startup>();
Server = new TestServer(hostBuilder);
Client = Server.CreateClient();
}
For Unit Testing, you can add Mapper.Reset() to your unit test class
[TearDown]
public void TearDown()
{
Mapper.Reset();
}
You can use automapper as Static API and Instance API ,
Mapper already initialized is common issue in Static API , you can use mapper.Reset()
where you initialized mapper but this this not an answer at all.
Just try with instance API
var students = db.Students.Include(s => s.Father);
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<Student, StudentViewModel>();
});
IMapper iMapper = config.CreateMapper();
return iMapper.Map<List<Student>, List<StudentViewModel>>(students);
Automapper 8.0.0 version
AutoMapper.Mapper.Reset();
Mapper.Initialize(
cfg => {
cfg.CreateMap<sourceModel,targetModel>();
}
);
You can simply use Mapper.Reset().
Example:
public static TDestination MapToObject<TSource, TDestination>(TSource Obj)
{
Mapper.Initialize(cfg => cfg.CreateMap<TSource, TDestination>());
TDestination tDestination = Mapper.Map<TDestination>(Obj);
Mapper.Reset();
return tDestination;
}
If you are using MsTest you can use the AssemblyInitialize attribute so that mapping gets configured only once for that assembly (here test assembly). This is generally added into to the base class of controller unit tests.
[TestClass]
public class BaseUnitTest
{
[AssemblyInitialize]
public static void AssemblyInit(TestContext context)
{
AutoMapper.Mapper.Initialize(cfg =>
{
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.EmailAddress));
});
}
}
I hope this answer helps
If you are using Mapper in UnitTest and your tests more then one, You may use Mapper.Reset()
`
//Your mapping.
public static void Initialize()
{
Mapper.Reset();
Mapper.Initialize(cfg =>
{
cfg.CreateMap<***>
}
//Your test classes.
[TestInitialize()]
public void Initialize()
{
AutoMapping.Initialize();
}`
private static bool _mapperIsInitialized = false;
public InventoryController()
{
if (!_mapperIsInitialized)
{
_mapperIsInitialized = true;
Mapper.Initialize(
cfg =>
{
cfg.CreateMap<Inventory, Inventory>()
.ForMember(x => x.Orders, opt => opt.Ignore());
}
);
}
}

NHibernate - one-to-many just not working with SQLite

TL;DR;
NHibernate reverse relationship is working on Azure-SQL and MSSQL2012 but not with SQLite
Description:
I am currently Unittesting my Asp.Net MVC App and set up my Unittest with FluentMigrator on SQLite.
After creating the Database I set up some base entries I need.
One of those is a Product.
A Product has many ProductSuppliers and a ProductSupplier has many ProductSupplierPrices
public class Product
{
public virtual long Id { get; set; }
public virtual string Number { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
//more properties
public virtual IList<ProductSupplier> Suppliers { get; set; }
//more properties
}
public class ProductSupplier
{
public virtual long Id { get; set; }
public virtual Product Product { get; set; }
public virtual Supplier Supplier { get; set; }
public virtual IList<ProductSupplierPrice> Prices { get; set; }
}
public class ProductSupplierPrice : IHaveId
{
public virtual long Id { get; set; }
public virtual ProductSupplier ProductSupplier { get; set; }
public virtual decimal FromAmount { get; set; }
public virtual decimal Price { get; set; }
}
Setup:
Create Supplier
Create Product
Create ProductSupplier
Create ProductSupplierPrice
Test:
Product product = this.session.Load<Product>((long)1);
ProductSupplier productSupplier = product.Suppliers.First(); //<-- Suppliers are null; therefore throws an exception
If I load them seperately to check the relationships:
productSupplierPrice.ProductSupplier <--- Correct Supplier
productSupplier.Prices <-- Null
productSupplier.Product <-- Product with Id 1
product.Suppliers <-- Null
So to me it seems, that the many-to-one direction works correctely, but the one-to-many (reverse relation) is not working.
The Problem exists only in my Unittest (SQLite) the App itself runs on Azure-SQL and is working fine.
EDIT:
Mappings with FluentnHibernate
public class ProductMap : ClassMap<Product>
{
public ProductMap()
{
Id(x => x.Id);
HasMany(x => x.Suppliers).Inverse().Cascade.DeleteOrphan().BatchSize(20);
//many more mappings
}
}
public ProductSupplierMap()
{
Id(x => x.Id);
References(x => x.Product);
References(x => x.Supplier);
Map(x => x.IsMainSupplier);
Map(x => x.SupplierProductNumber);
Map(x => x.CopperSurcharge);
HasMany(x => x.Prices).Inverse().Cascade.DeleteOrphan().BatchSize(20);
}
public ProductSupplierPriceMap()
{
Id(x => x.Id);
References(x => x.ProductSupplier);
Map(x => x.FromAmount);
Map(x => x.Price);
}
Edit2 - Creating the DB-Entries:
Product product = new Product()
{
Type = ProductType.Purchase,
Dispatcher = session.Load<Employee>(employeeId),
Number = "100.10-1000",
Name = "Testproduct",
//Lots of Properties
Suppliers = new List<ProductSupplier>()
};
session.SaveOrUpdate(product);
ProductSupplier productSupplier = new ProductSupplier()
{
Product = product,
Supplier = session.Load<Supplier>((long)1),
IsMainSupplier = true,
SupplierProductNumber = "Artikel123456",
CopperSurcharge = CopperSurchargeType.DEL700,
Prices = new List<ProductSupplierPrice>()
};
session.Save(productSupplier);
ProductSupplierPrice productSupplierPrice = new ProductSupplierPrice()
{
ProductSupplier = productSupplier,
FromAmount = 1,
Price = 5
};
session.Save(productSupplierPrice);
EDIT 3.1:
public static ISession InitializeDatabase()
{
NHibernateSessionHolder.CreateSessionFactory();
session = NHibernateSessionHolder.OpenSession();
CreateBaseEntries(); //Creates Employees, Supplier, Customer etc
return session;
}
Based on the Ayende's article you need to clear the session between insert/update and querying:
session.Clear();
Seems to be a session management, I'm not sure why the session should be clean, but the session is providing your original instance (the same you provided for saving, stored on the session cache) instead a proxy for lazy-loading.
private long CreatePurchaseOrder()
{
session.Clear();
var product = this.session.Load<Product>((long)1);
var productSupplier = product.Suppliers.First();
var productSupplierPrice = productSupplier.Prices.First();
return 0;
}
Sorry for late reply
In your unit test, you are using same session for creating and fetching entities. This is not right as subsequent fetch returns entities from first level cache which do not have their graph set up properly.
So....either use different sessions OR as a quick fix, I have added "session.Clear()" in the method "InitializeDatabase()" of "DatabaseSetUpHelper". Clearing the session clears first level cache and force NH to fetch data from DB again and the resulting entities have their graph set up properly.
public static ISession InitializeDatabase()
{
NHibernateSessionHolder.CreateSessionFactory();
session = NHibernateSessionHolder.OpenSession();
CreateBaseEntries();
session.Clear(); // notice this!!! this clears first level cache of session, thus forcing fetching of data from DB
return session;
}
Note: My quick-fix is not final solution, it is there just show how session behaves. In proper solution, you must use different sessions.

Fluent NHibernate fluent mapping object and a collection of object

I'm a newbie to Fluent Nhibernate (FNH) or NHibernate (or even ORMs) in general. I have a pet project that I'm using to learn FNH and I'm stuck with, what looks like a design issue. Its a basic Library Management System and I have objects like books, users, booksize(!) etc. For instance, I have a BookSize class and its manager BookSizesManager which hold a list of BookSize objects. Could please anyone advise me how to go about creating ClassMap for both of them such that my database (for testing purpose, say a SQLite database) would have only one table called 'BookSizes' and would list all the BookSize objects in BookSizeManager?
My current implementation is as followed and flawed as it produces two tables 1. BookSize 2. BookSizes (from BookSizeManager Map).
My BookSize Class
public class BookSize
{
public virtual string ID { get; set; }
public virtual string Name { get; set; }
public virtual double Length { get; set; }
public virtual double Width { get; set; }
}
Corresponding ClassMap
public class BookSizeMap : ClassMap<BookSize>
{
public BookSizeMap()
{
Id(x => x.ID);
Map(x => x.Name);
Map(x => x.Length);
Map(x => x.Width);
}
}
My BookSizesManager Class
public class BookSizesManager
{
public virtual string Id { get; set; }
private IList<BookSize> m_bookSizes = new List<BookSize>();
public virtual IList<BookSize> Items
{
get { return new ReadOnlyCollection<BookSize>(m_bookSizes); }
set { if(value != null) m_bookSizes = value; }
}
public virtual void Add(BookSize size)
{
if (size != null)
{
m_bookSizes.Add(size);
}
}// Also contains other unimplemented CRUD methods, but haven't listed them here to reduce 'noise'
}
Corresponding ClassMap
public class BookSizesManagerMap : ClassMap<BookSizesManager>
{
public BookSizesManagerMap()
{
Id(x => x.Id);
HasMany(x => x.Items)
.Cascade.All();
Table("BookSizes");
}
}
Any help is greatly appreciated. Thanks in advance.
i would get rid of BookSizesManager completly and use the session directly and specify the tablename explicitly
public class BookSizeMap : ClassMap<BookSize>
{
public BookSizeMap()
{
Table("BookSizes");
...
}
}
BookSizesManager.Add(booksize); becomes session.Save(booksize);
BookSizesManager.Get(booksizeId); becomes session.Get(booksizeId);

Resources