I have problem with my Nhibernate app.
I have class called Rozmiar, and class called Symbol.
Symbol contains List as property.
How can I save it into database using NHibernate?
My code (doesn't working properly):
SYMBOL CLASS:
public class Symbol
{
public virtual int Id { get; set; }
public virtual string Nazwa { get; set; }
public virtual bool Sitodruk { get; set; }
public virtual List<Rozmiar> Rozmiar { get; set; }
public Symbol() { }
public Symbol(string nazwa, List<Rozmiar> lista)
{
using (ISession sesja = Program.baza.SessionFactory.OpenSession())
{
using (ITransaction transaction = sesja.BeginTransaction())
{
Symbol s = new Symbol();
s.Nazwa = nazwa;
s.Rozmiar = lista;
sesja.Save(s);
transaction.Commit();
}
}
}
}
ROZMIAR CLASS:
public class Rozmiar
{
public virtual int Id { get; set; }
public virtual string Nazwa { get; set; }
public Rozmiar() { }
public Rozmiar(string nazwa)
{
using (ISession sesja = Program.baza.SessionFactory.OpenSession())
{
using (ITransaction transaction = sesja.BeginTransaction())
{
Rozmiar r = new Rozmiar();
r.Nazwa = nazwa;
sesja.Save(r);
transaction.Commit();
}
}
}
}
MAP:
public class RozmiarMap : ClassMap<Rozmiar>
{
public RozmiarMap()
{
Table("Rozmiar");
SchemaAction.All();
Id(x => x.Id).GeneratedBy.Identity();
Map(X => X.Nazwa).Not.Nullable();
}
}
public class SymbolMap : ClassMap<Symbol>
{
public SymbolMap()
{
Table("Symbol");
SchemaAction.All();
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Nazwa).Not.Nullable();
HasMany(x => x.Rozmiar).KeyColumn("IdRozmiar");
}
}
And method to generate sample objects:
public static void Generuj()
{
List<Rozmiar> listA = new List<Rozmiar>();
listA.Add(new Rozmiar("750 mm"));
listA.Add(new Rozmiar("900 mm"));
listA.Add(new Rozmiar("1050 mm"));
listA.Add(new Rozmiar("1200 mm"));
Symbol a1 = new Symbol("A-1", listA);
Symbol a2 = new Symbol("A-2", listA);
Symbol a3 = new Symbol("A-3", listA);
}
In debugging mode, I saw, that listA contains 4 objects, but properties of those objects ale Id = 0 and Nazwa = null :( Maybe there is a main problem...
I also don't know, if my mapping is set properly.
BTW. Connections settings and config are ok, because i have other classes in this code, and it saves properly into database.
In the constructor of Rozmiar with string parameter, you are creating different (new) instance of the Rozmiar. So while you are adding into the listA instance created by
new Rozmiar("750 mm")
persisted is different object
Rozmiar r = new Rozmiar();
Also, change your mapping from List<Rozmiar> into IList<Rozmiar>. See here: 6.1. Persistent Collections the complete list of supported interfaces for collection mapping
Related
My setup is that I have a basket which contains items. An item is made up of a product and a size. Products have a many to many relationship with sizes so that I can verify that a given size is valid for a given product. I would like to be able to add an item to the basket, perform some validation and save to the database.
I have created a demo program to demonstrate the problem I am having. When the program runs there is already a basket saved to the database (see the DBInitializer). It has one item which is a large foo. In the program you can see that I load the basket, load a small size and a bar product. I add the large bar to the basket. The basket does some internal validation and I save to the database. This works without error.
The problem comes when I try to add a product that already exists in the database with a different size. Hence if we try to add a large bar to the basket and save we get a null reference exception. This is not the behaviour I would like because a basket which contains 2 items, a large foo and a small foo, is perfectly valid.
I'm pretty sure the problem is to do with the fact that we have already loaded foo in the basket through eager loading. I've tried commenting out the eager loading for the basketitems and this works. However if possible I would like a solution which keeps the eager loading.
Notes: I have added an extra method to my dbcontext class which is int SaveChanges(bool excludeReferenceData). This stops extra product and size records being saved back to the database. I've made all my constructors, getters and setters public to make it easier to replicate my problem. My demo code was created on a console app targeting .net framework 4.5.2. The version of Entity framework is 6.2.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
using static Demo.Constants;
namespace Demo
{
public static class Constants
{
public static int BasketId => 1;
public static int SmallId => 1;
public static int LargeId => 2;
public static int FooId => 1;
public static int BarId => 2;
}
public class Program
{
public static void Main()
{
using (var context = new AppContext())
{
var customerBasket = context.Baskets
.Include(b => b.Items.Select(cbi => cbi.Product))
.Include(b => b.Items.Select(cbi => cbi.Size))
.SingleOrDefault(b => b.Id == BasketId);
var size = context.Sizes.AsNoTracking()
.SingleOrDefault(s => s.Id == SmallId);
context.Configuration.ProxyCreationEnabled = false;
var product = context
.Products
.AsNoTracking()
.Include(p => p.Sizes)
.SingleOrDefault(p => p.Id == BarId);
//changing BarId to FooId in the above line results in
//null reference exception when savechanges is called.
customerBasket.AddItem(product, size);
context.SaveChanges(excludeReferenceData: true);
}
Console.ReadLine();
}
}
public class Basket
{
public int Id { get; set; }
public virtual ICollection<Item> Items { get; set; }
public Basket()
{
Items = new Collection<Item>();
}
public void AddItem(Product product, Size size)
{
if (itemAlreadyExists(product, size))
{
throw new InvalidOperationException("item already in basket");
}
var newBasketItem = Item.Create(
this,
product,
size);
Items.Add(newBasketItem);
}
private bool itemAlreadyExists(Product product, Size size)
{
return Items.Any(a => a.ProductId == product.Id && a.SizeId == size.Id);
}
}
public class Item
{
public Guid Id { get; set; }
public int BasketId { get; set; }
public virtual Product Product { get; set; }
public int ProductId { get; set; }
public virtual Size Size { get; set; }
public int SizeId { get; set; }
public Item()
{
}
public string getDescription()
{
return $"{Product.Name} - {Size.Name}";
}
internal static Item Create(Basket basket
, Product product,
Size size)
{
Guid id = Guid.NewGuid();
if (!product.HasSize(size))
{
throw new InvalidOperationException("product does not come in size");
}
var basketItem = new Item
{
Id = id,
BasketId = basket.Id,
Product = product,
ProductId = product.Id,
Size = size,
SizeId = size.Id
};
return basketItem;
}
}
public class Product : IReferenceObject
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ProductSize> Sizes { get; set; }
public Product()
{
Sizes = new Collection<ProductSize>();
}
public bool HasSize(Size size)
{
return Sizes.Any(s => s.SizeId == size.Id);
}
}
public class ProductSize : IReferenceObject
{
public int SizeId { get; set; }
public virtual Size Size { get; set; }
public int ProductId { get; set; }
}
public class Size : IReferenceObject
{
public int Id { get; set; }
public string Name { get; set; }
}
public class AppContext : DbContext
{
public DbSet<Basket> Baskets { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Size> Sizes { get; set; }
public AppContext()
: base("name=DefaultConnection")
{
Database.SetInitializer(new DBInitializer());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Basket>()
.HasMany(c => c.Items)
.WithRequired()
.HasForeignKey(c => c.BasketId)
.WillCascadeOnDelete(true);
modelBuilder.Entity<Item>()
.Property(c => c.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
modelBuilder.Entity<Item>()
.HasKey(c => new { c.Id, c.BasketId });
modelBuilder.Entity<ProductSize>()
.HasKey(c => new { c.ProductId, c.SizeId });
base.OnModelCreating(modelBuilder);
}
public int SaveChanges(bool excludeReferenceData)
{
if(excludeReferenceData)
{
var referenceEntries =
ChangeTracker.Entries<IReferenceObject>()
.Where(e => e.State != EntityState.Unchanged
&& e.State != EntityState.Detached);
foreach (var entry in referenceEntries)
{
entry.State = EntityState.Detached;
}
}
return SaveChanges();
}
}
public interface IReferenceObject
{
}
public class DBInitializer: DropCreateDatabaseAlways<AppContext>
{
protected override void Seed(AppContext context)
{
context.Sizes.Add(new Size { Id = LargeId, Name = "Large" });
context.Sizes.Add(new Size { Id = SmallId, Name = "Small" });
context.Products.Add(
new Product
{
Id = FooId,
Name = "Foo",
Sizes = new Collection<ProductSize>()
{
new ProductSize{ProductId = FooId, SizeId = LargeId},
new ProductSize{ProductId = FooId, SizeId =SmallId}
}
});
context.Products.Add(new Product { Id = BarId, Name = "Bar",
Sizes = new Collection<ProductSize>()
{
new ProductSize{ProductId = BarId, SizeId = SmallId}
}
});
context.Baskets.Add(new Basket
{
Id = BasketId,
Items = new Collection<Item>()
{
new Item
{
Id = Guid.NewGuid(),
BasketId =BasketId,
ProductId = FooId,
SizeId = LargeId
}
}
});
base.Seed(context);
}
}
}
When you use AsNoTracking, this tells EF to not include the objects being loaded into the DbContext ChangeTracker. You normally want to do this when you load data to be returned and know you aren't going to want to save it back at that point. Thus, I think you just need to get rid of AsNoTracking on all of your calls and it should work fine.
In my project I have a base class (not mapped):
public abstract class BaseEntity
{
public virtual string Name { get; set; }
public virtual string Description { get; set; }
}
Also I have a few inherited classes (they look all almost the same, so here is a code and map for only one)
public class User : BaseEntity
{
public virtual int UserId { get; set; }
public virtual string Login { get; set; }
public virtual string PasswordHash { get; set; }
public virtual ISet<BaseEntity> Entities { get; set; }
}
public class UserMap : ClassMap<User>
{
public UserMap()
{
this.Id(x => x.UserId);
this.Map(x => x.Login);
this.Map(x => x.PasswordHash);
this.HasManyToMany<BaseEntity>(x => x.Entities);
}
}
Next, I have a NHibernateHelper:
public class NHibernateHelper
{
public static ISession OpenSession()
{
ISessionFactory sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(#"someconstring")
.ShowSql()
)
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<User>())
.ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(false, true))
.BuildSessionFactory();
return sessionFactory.OpenSession();
}
}
And here is a question:
How can I exclude BaseEntity class from mapping, if I need table like EnitiyToEntity in my Database for many-to-many relationship?
Take a look to this:
https://www.codeproject.com/Articles/232034/Inheritance-mapping-strategies-in-Fluent-Nhibernat
If I understand your question the solution should be to implement TPC (Table per concrete class).
By the way, in your mapping you have to use the concrete type for HasManyToMany.
For example (I supposed your user is referenced to many groups):
HasManyToMany<Group>(x => x.Entities).Table("UsersGroups");
where the Group class is something like this:
public class Group : BaseEntity
{
public virtual int GroupId { get; set; }
public virtual string PasswordHash { get; set; }
public virtual ISet<BaseEntity> Members { get; set; }
}
And in the GroupMap class you can reference the users like this:
HasManyToMany<User>(x => x.Members).Table("UsersGroups");
If you reference a class you have to map it. So map Entity as ClassMap and all the others as SubclassMap. They will end up as union subclass which is one table per class. Unfortunatly you can not map a hasmanytoany with FNH.
You can map it as hasmanytomany and work around it:
var config = new Configuration();
config.BeforeBindMapping += BeforeBindMapping;
_config = Fluently
.Configure(config)
...
private void BeforeBindMapping(object sender, NHCfg.BindMappingEventArgs e)
{
var userclass = e.Mapping.RootClasses.FirstOrDefault(rc => rc.name.StartsWith(typeof(User).FullName));
if (userclass != null)
{
HbmSet prop = (HbmSet)paymentclass.Properties.FirstOrDefault(rc => rc.Name == "Entities");
prop.Item = new HbmManyToAny // == prop.ElementRelationship
{
column = new[]
{
new HbmColumn { name = "entityType", notnull = true, notnullSpecified = true },
new HbmColumn { name = "entity_id", notnull = true, notnullSpecified = true }
},
idtype = "Int64",
metatype = "String",
metavalue = typeof(Entity).Assembly.GetTypes()
.Where(t => !t.IsInterface && !t.IsAbstract && typeof(Entity).IsAssignableFrom(t))
.Select(t => new HbmMetaValue { #class = t.AssemblyQualifiedName, value = t.Name })
.ToArray()
};
}
}
The following are the entity classes to make more understanding of relationships:
public class EmployeeCv : UserEntity
{
public byte ProfileImage { get; set; }
public virtual List<Header> Headers { get; set; }
public virtual List<ProjectExperience> ProjectExperiences { get; set; }
public virtual List<Tag> Tags { get; set; } //many to many relationship between employeeCv and tags
[NotMapped]
public List<TagsByTypes> TagsbyTypes
{
get
{
List<TagsByTypes> ListOfTagTypes = new List<TagsByTypes>();
if (Tags != null)
{
var GroupedList = Tags.GroupBy(x => x.TagType.Title).ToList().Select(grp => grp.ToList());
foreach (var currentItem in GroupedList)
{
var TagType = new TagsByTypes()
{
Title = currentItem.FirstOrDefault().TagType.Title,
Tags = currentItem
};
ListOfTagTypes.Add(TagType);
}
return ListOfTagTypes;
}
else
return null;
}
}
}
public class Tag : AuditableEntity<int>
{
public string Title { get; set; }
public virtual List<EmployeeCv> EmployeeCv { get; set; }
public virtual TagType TagType { get; set; }
//To post Id's Not added to the database
[NotMapped]
public int TagTypeId { get; set; }
[NotMapped]
public int EmployeeCv_Id { get; set; }
}
public class TagType : AuditableEntity<int>
{
public string Title { get; set; }
public virtual List<Tag> Tags { get; set; }
}
I am writing a function to add new tag to the employeeCv based on the existing tag type. I have got Unit of work and Repositories setup to add/update/delete records in DB. Here is my implementation:
public void UpdateEmployeeCVWithTag(Tag tag)
{
using (var repository = new UnitOfWork<EmployeeCv>().Repository)
{
var EmployeeCv = repository.GetSingleIncluding(tag.EmployeeCv_Id,
x => x.Headers, x => x.Tags,
x => x.ProjectExperiences,
x => x.ProjectExperiences.Select(p => p.AssociatedProject),
x => x.ProjectExperiences.Select(p => p.ProjectSkills));
//x => x.ProjectExperiences.Select(p => p.ProjectSkillTags.Select(s => s.AssociatedSkill)));
//tag.TagType = EmployeeCv;
var repositoryTagType = new UnitOfWork<TagType>().Repository;
var tagtype = repositoryTagType.GetItemById(tag.TagTypeId);
tag.TagType = tagtype; //even after assignment new tagtype is creating everytime code runs
//repositoryTag.UpdateItem(tagtype);
EmployeeCv.Tags.Add(tag);
//EmployeeCv.ProjectExperiences[projectId - 1].ProjectSkills.Add(tag);
repository.UpdateItem(EmployeeCv);
}
}
This function works correctly except one issue. It is creating a new TagType in the database and ignoring the one that already exist. Below is my updateItem code in the repository classs:
public virtual void UpdateItem(TEntity entityToUpdate)
{
var auditableEntity = entityToUpdate as IAuditableEntity;
if (auditableEntity != null)
{
auditableEntity.UpdatedDate = DateTime.Now;
}
//_context
//Attach(entityToUpdate);
_context.Entry(entityToUpdate).State = EntityState.Modified;
_context.SaveChanges();
}
My guess without seeing the full functionality, is that you are using different context for this.
You should update the foreign key not the entire object so there is no need to add the entire TagType object since the tagTypeId is already set. The foreign key should work as is.
Please look into this link for further information.
Here is my object and mapping:
public class candleStim
{
public virtual int Id { get; set; }
public virtual int candleNumber { get; set; } //the number of the candle from the data set, should correspond with number of minutes into testing on 1 min candles
public virtual DateTime date { get; set; }
public virtual decimal open { get; set; }
public virtual decimal high { get; set; }
public virtual decimal low { get; set; }
public virtual decimal close { get; set; }
public virtual List<EMA> EMAs { get; set; } //List all EMAs calculated.
public virtual List<SMA> SMAs { get; set; }
}
public class candleStimMap : ClassMap<candleStim>
{
public candleStimMap()
{
Id(x => x.Id);
Map(x => x.candleNumber);
Map(x => x.date);
Map(x => x.open);
Map(x => x.high);
Map(x => x.low);
Map(x => x.close);
HasMany<SMA>(x => x.SMAs)
.Component(c =>
{
c.Map(x => x.SimpleMovingAverage);
c.Map(x => x.periods);
}).AsSet();
HasMany<EMA>(x => x.EMAs)
.Component(c =>
{
c.Map(x => x.ExponentialMovingAverage);
c.Map(x => x.periods);
}).AsSet();
Table("candle_Simulation");
} //end public candleStimMap()
Here is my current attempt at saving (which fails)
foreach (candleStim c in calculatedCandles)
{
using (var session = NHibernateHelper.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
candleStim cc = new candleStim();
cc.date = c.date;
cc.open = c.open;
cc.high = c.high;
cc.low = c.low;
cc.close = c.close;
//The below 2 lines are where the problem arises
//if these are standard objects, no errors show up
cc.EMAs = c.EMAs;
cc.SMAs = c.SMAs;
session.Save(c);
transaction.Commit();
}
}
counter++;
}
The error msg:{"Unable to cast object of type 'NHibernate.Collection.Generic.PersistentGenericSet1[Midas_FOREX_Engine.Indicators.SMA]' to type 'System.Collections.Generic.List1[Midas_FOREX_Engine.Indicators.SMA]'."}
So I've got a mismatch of list types. How would I make a list of the NHibernate.Collection.Generic.PersistentGenericSet type and save the values?
The only fields Im saving to the database from the SMA-EMA is a decimal of the value and an integer of the number of periods.
Thank you!!
When you map a HasMany property to AsSet, you should use an ISet type on it.
So you property will become like:
public virtual Iesi.Collections.ISet<EMA> EMAs { get; set; }
I strongly recommend you to read this NHibernate docs chapter (if you still didn't).
http://nhibernate.info/doc/nh/en/index.html#collections-persistent
It will clarify you about how to choose the best property type / mapping to your one-to-many situation.
I have an issue with mapping, simplified my relationship looks like this.
I have parent class:
public abstract class DocumentType
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
and two subclasses:
public class UploadedFileDocument : DocumentType
{
}
public class ApplicationFormDocument : DocumentType
{
}
mapped like this:
public DocumentTypeMap()
{
Schema("Core");
Id(x => x.Id);
Map(x => x.Name).Length(128).Not.Nullable();
DiscriminateSubClassesOnColumn("Type");
}
public class UploadedFileDocumentMap : SubclassMap<UploadedFileDocument>
{
}
public class ApplicationFormDocumentMap : SubclassMap<ApplicationFormDocument>
{
}
Then I have another entity with a FK to DocumentType, mapped like this:
public FileConversionMap()
{
Schema("Core");
Id(x => x.Id);
References(x => x.Application).Not.Nullable();
References(x => x.DocumentType).Not.Nullable().Fetch.Select();
}
my issue is, when I retrieve rows from the DB like this:
Session.Query<FileConversion>().AsQueryable();
all the rows come back with the DocumentType being of type DocumentType, not of the child type (ie the actual type of that property, ie. when i do .GetType(), either UploadedFileDocument or ApplicationFormDocument)
Apologies if this is just me being dim. But how can I determine which type of DocumentType I have ... is my mapping wrong?
When you look at your generated SQL (adding .ShowSQL() to your .Database method), do you see the Type being entered? You should see something similar to:
INSERT
INTO
"Core_DocumentType"
(Name, Type)
VALUES
(#p0, 'ApplicationFormDocument');
select
last_insert_rowid();
#p0 = 'afd' [Type: String (0)]
Using the mappings you provided, it looks fine and I could return the DocumentType (using SQLite) just fine.
Here's the code I used to reproduce it. I didn't have your FileConversion object, so please verify that it matches what you need.
DocumentType
public class DocumentType
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
public class DocumentTypeMap : ClassMap<DocumentType>
{
public DocumentTypeMap()
{
GenerateMap();
}
void GenerateMap()
{
Schema("Core");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Name).Length(128).Not.Nullable();
DiscriminateSubClassesOnColumn("Type");
}
}
UploadFileDocument/ApplicationFormDocument
public class UploadedFileDocument : DocumentType
{
public virtual string ContentType { get; set; }
}
public class ApplicationFormDocument : DocumentType
{
}
public class UploadFileDocumentMap :
SubclassMap<UploadedFileDocument>
{
public UploadFileDocumentMap()
{
GenerateMap();
}
void GenerateMap()
{
Map(x => x.ContentType);
}
}
public class ApplicationFormDocumentMap :
SubclassMap<ApplicationFormDocument>
{
}
FileConversion
public class FileConversion
{
public virtual int Id { get; set; }
public virtual DocumentType DocumentType { get; set; }
}
public class FileConversionMap : ClassMap<FileConversion>
{
public FileConversionMap()
{
GenerateMap();
}
void GenerateMap()
{
Schema("Core");
Id(x => x.Id).GeneratedBy.Identity();
References(x => x.DocumentType).Not.Nullable().Fetch.Select();
}
}
The tests I used (using machine.specifications):
Context
public class when_discriminating_on_subclass
{
static IList<FileConversion> results;
Establish context = () =>
{
using (var session = DataConfiguration.CreateSession())
{
using (var transaction = session.BeginTransaction())
{
var upload = new UploadedFileDocument
{ Name = "uploaded", ContentType = "test" };
var form = new ApplicationFormDocument
{ Name = "afd" };
session.Save(form);
session.Save(upload);
var formConversion =
new FileConversion { DocumentType = form };
var uploadConversion =
new FileConversion { DocumentType = upload };
session.Save(formConversion);
session.Save(uploadConversion);
transaction.Commit();
}
using (var transaction = session.BeginTransaction())
{
results = session.Query<FileConversion>().AsQueryable().ToList();
transaction.Commit();
}
}
};
Specifications
It should_return_two_results = () =>
results.Count.ShouldEqual(2);
It should_contain_one_of_type_uploaded_file = () =>
results
.Count(x => x.DocumentType.GetType() == typeof(UploadedFileDocument))
.ShouldEqual(1);
It should_contain_one_of_type_application_form = () =>
results
.Count(x => x.DocumentType.GetType() == typeof(ApplicationFormDocument))
.ShouldEqual(1);
}
Debugging through the assertions, I can see that the collection comes back with the two types:
Are you casting them back to the base type anywhere in your mappings or classes?