fluent-nhibernate: not getting the records - fluent-nhibernate

I have an entity like
public class SKU
{
//public int Id { get; set; }
public string FactoruCode { get; set; }
public string Ptoduct { get; set; }
}
and mapping defined as
public class SKUMap : ClassMap<SKU>
{
public SKUMap()
{
Table("MST_PRODUCT");
Not.LazyLoad();
Id(x => x.Ptoduct).GeneratedBy.Assigned();
Map(x => x.Ptoduct, "PRODUCT_NAME");
Map(x => x.FactoruCode, "FACTORY_CODE");
}
}
and retrieving the records like
class Program
{
static void Main()
{
var sessionFactory = CreateSessionFactory();
using (var session = sessionFactory.OpenSession())
{
using (session.BeginTransaction())
{
var skus = session.CreateCriteria(typeof(SKU)).List<SKU>();
foreach (var sku in skus)
{
Console.WriteLine(sku.Ptoduct);
}
}
}
}
private static ISessionFactory CreateSessionFactory()
{
var cfg = OracleClientConfiguration.Oracle10
.ConnectionString(c =>
c.Is(
#"DATA SOURCE=SERVER_NAME;PERSIST SECURITYINFO=True;USER ID=USER_ID;Password=PWD"));
return Fluently.Configure()
.Database(cfg).Mappings(m => m.FluentMappings.AddFromAssemblyOf<Program>())
.ExposeConfiguration(BuildSchema).BuildSessionFactory();
}
private static void BuildSchema(NHibernate.Cfg.Configuration config)
{
new SchemaExport(config).Create(false, true);
}
}
but the table has more columns than specified for entity. This code executes well, but I'm not able to get the list of SKUs (table has more than 8000 rows).
Please help me to understand the problem.

Your SKU map is wrong. Why have you defined PRODUCT_NAME as an Id column? You need to fix it by setting the Id to an Id column (which you have commented out):
Id(x => x.Id, "NAME_OF_YOUR_ID_COLUMN_HERE").GeneratedBy.Assigned();
Map(x => x.Ptoduct, "PRODUCT_NAME");
If PRODUCT_NAME is indeed the Id, you need to set it like this:
Id(x => x.Ptoduct, "PRODUCT_NAME").GeneratedBy.Assigned();
and remove the other line:
Map(x => x.Ptoduct, "PRODUCT_NAME");
Also, if your database has more fields or tables then you are mapping, it can give you many errors. To resolve them, you need to set use_proxy_validator to false in your configuration.
EDIT:
NHibernate requires an Id column to work properly. I don't even know that if it does work without actually having a column declared as an Id column. Even if you declare Ptoduct as an Id column, you will not be able to properly query the database as querying for any of all objects with the same Ptoduct will return the topmost object.

Related

fluentnhibernate hasmanytomany same identifier exception

I have a many-to-many relationship between two objects (Application, Query). I have constructed the map to have a HasManyToMany mapping in both object maps.
The first time I use the SaveOrUpdate the application, it works fine, and the entry is placed correctly in the join table.
However, the second time I use it, I get the error: 'a different object with the same identifier was already associated with the session'.
The exception is thrown in the last couple of lines of the addQuery code shown below.
Here are the entities and the maps. Any suggestions would be greatly appreciated.
public class Application
{
public virtual int id { get; set; }
public virtual string name { get; set; }
public virtual IList<Query> queries { get; set; }
public Application()
{
this.queries = new List<Query>();
}
public virtual void AddQuery(Query qry)
{
qry.applicationsUsedIn.Add(this);
queries.Add(qry);
}
}
public class Query
{
public virtual int id { get; protected set; }
public virtual string name { get; set; }
public virtual string query { get; set; }
public virtual IList<QueryParameters> parameters { get; set; }
public virtual IList<Application> applicationsUsedIn { get; set; }
public Query()
{
this.parameters = new List<QueryParameters>();
this.applicationsUsedIn = new List<Application>();
}
public virtual void AddParameter(QueryParameters qp)
{
qp.query = this;
this.parameters.Add(qp);
}
}
public class ApplicationMap : ClassMap<Application>
{
public ApplicationMap()
{
Table("dbo.Applications");
Id(x => x.id).Column("id");
Map(x => x.name).Column("name");
HasManyToMany(x => x.queries)
.Table("dbo.ApplicationsQueries")
.ParentKeyColumn("appid")
.ChildKeyColumn("qryid")
.Not.LazyLoad()
.Cascade.SaveUpdate();
}
}
public class QueryMap : ClassMap<Query>
{
public QueryMap()
{
Table("dbo.Queries");
Id(x => x.id);
Map(x => x.name);
Map(x => x.query);
HasMany(x => x.parameters)
.Cascade.All()
.Inverse();
HasManyToMany(x => x.applicationsUsedIn)
.Table("dbo.ApplicationsQueries")
.ParentKeyColumn("qryid")
.ChildKeyColumn("appid")
.Inverse()
.Cascade.SaveUpdate()
.Not.LazyLoad();
}
}
public void addQuery(string appname, string qryname, string qrystr)
{
Application app = getApplication(appname);
if (null == app)
{
app = addApplication(appname);
}
Query qry = getQuery(appname, qryname);
if (null == qry)
{
using (ISessionFactory isf = getSessionFactory())
{
using (var sess = isf.OpenSession())
{
using (var tran = sess.Transaction)
{
tran.Begin();
qry = new Query();
qry.name = qryname;
qry.query = qrystr;
sess.Save(qry);
tran.Commit();
}
}
}
}
if (!app.queries.Contains(qry))
{
using (ISessionFactory isf = getSessionFactory())
{
using (var sess = isf.OpenSession())
{
using (var tran = sess.Transaction)
{
tran.Begin();
app.AddQuery(qry);
//This is where the exception is thrown
sess.SaveOrUpdate(app);
tran.Commit();
}
}
}
}
}
UPDATED CODE in case it helps someone else
public ApplicationMap()
{
Table("dbo.Applications");
Id(x => x.id).Column("id");
Map(x => x.name).Column("name");
HasManyToMany(x => x.queries)
.Table("dbo.ApplicationsQueries")
.ParentKeyColumn("appid")
.ChildKeyColumn("qryid")
.LazyLoad();
}
public QueryMap()
{
Table("dbo.Queries");
Id(x => x.id);
Map(x => x.name);
Map(x => x.query);
HasMany(x => x.parameters)
.Cascade.All()
.Inverse();
HasManyToMany(x => x.applicationsUsedIn)
.Table("dbo.ApplicationsQueries")
.ParentKeyColumn("qryid")
.ChildKeyColumn("appid")
.Inverse()
.LazyLoad();
}
public void addQuery(string appname, string qryname, string qrystr)
{
using (ISessionFactory isf = getSessionFactory())
{
using (var sess = isf.OpenSession())
{
using (var tran = sess.Transaction)
{
tran.Begin();
var critapp = sess.CreateCriteria<Application>()
.Add(Restrictions.Eq("name", appname));
Application app = (Application)critapp.UniqueResult();
if (null == app)
{
app = new Application();
app.name = appname;
sess.Save(app);
}
var critqry = sess.CreateCriteria<Query>()
.Add(Restrictions.Eq("name", qryname));
Query qry = (Query)critqry.UniqueResult();
if (null == qry)
{
qry = new Query();
qry.name = qryname;
qry.query = qrystr;
sess.Save(qry);
}
if (!app.queries.Contains(qry))
{
app.AddQuery(qry);
}
tran.Commit();
}
}
}
}
The problem here is hidden in many opened sessions for (in fact) one operation. This is not the correct way how to execute this insert/updates. We should always wrap a set of operations relying on each other, into ONE session, one transaction.
So what happened is that here we've got the Query recieved like this:
Query qry = getQuery(appname, qryname);
We have an object, which is (was) part of the session, which just ran out of the scope. The qry instance is fully populated now because
it is an existing object (loaded from DB)...
the mapping of the collection IList<Application> applicationsUsedIn (see your mapping) is .Not.LazyLoad()
and the same is true for other Not.LazyLoad() mappings...
So once we come to the last transaction (and its own session) ... our objects could be deeply populated... having the same IDs as loaded objects inside of that last session
To solve the issue quickly:
Open the session at the begining of the operation
Open the transaction at the begining
Call Save() only, if we do have new object
For objects retrieved via getQuery(), getApplication() (beeing in the same session) DO NOT call the SaveOrUpdate(). They are already in the session, and that's what the SaveOrUpdated in this case mostly does (put them in the session)
a) Call the transaction.Commit() and all the stuff will be properly persisted
b) Close the session at the end of operation
Note: I would change the mapping of your many-to-many
remove the Not.LazyLoad(). Lazy is what we mostly want..
remove Cascade because it is about the other end not about the pairing table. If this was intended then leave it

Fluent NHibernate One to Many - Transient Instance Exception

I am attempting to do a simple one to many mapping in fluent NHibernate, however i receive the following exception:
"NHibernate.TransientObjectException : object references an unsaved transient instance - save the transient instance before flushing or set cascade action for the property to something that would make it autosave. Type: Voter.Domain.Entities.VoteOption, Entity: Voter.Domain.Entities.VoteOption"
I have tried numerous using Cascade().All() - but this makes no difference.
Please help me to get this cascade working! Much time already wasted...
I have the following entities:
public class Vote
{
public Vote()
{
VoteOptions = new List<VoteOption>();
}
public virtual int Id { get; protected set; }
public virtual Guid VoteReference { get; set; }
public virtual string Title { get; set; }
public virtual string Description { get; set; }
public virtual DateTime ValidFrom { get; set; }
public virtual DateTime ValidTo { get; set; }
public virtual IList<VoteOption> VoteOptions { get; set; }
public virtual void AddOption(VoteOption voteOption)
{
VoteOptions.Add(voteOption);
}
public virtual void AddOptions(List<VoteOption> options)
{
foreach (var option in options.Where(option => VoteOptionAlreadyExists(option) == false))
{
VoteOptions.Add(option);
}
}
private bool VoteOptionAlreadyExists(VoteOption voteOption)
{
return VoteOptions.Any(x => x.Description == voteOption.Description);
}
}
public class VoteOption
{
public virtual int Id { get; protected set; }
public virtual string LongDescription { get; set; }
public virtual string Description { get; set; }
public virtual Vote Vote { get; set; }
}
And the following mappings:
public VoteMap()
{
Table("Vote");
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
Map(x => x.VoteReference).Column("VoteReference");
Map(x => x.Title).Column("Title").Not.Nullable();
Map(x => x.Description).Column("Description").Not.Nullable();
Map(x => x.ValidFrom).Column("ValidFrom").Not.Nullable();
Map(x => x.ValidTo).Column("ValidTo").Not.Nullable();
HasMany(x => x.VoteOptions).KeyColumn("Vote_Id").Cascade.All();
}
public class VoteOptionMap : ClassMap<VoteOption>
{
public VoteOptionMap()
{
Table("VoteOption");
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
Map(x => x.Description).Column("Description").Not.Nullable();
Map(x => x.LongDescription).Column("LongDescription").Not.Nullable();
References(x => x.Vote).Column("Vote_Id").Cascade.All();
}
}
And the following SQL Server database tables:
CREATE TABLE dbo.Vote
(
Id INT IDENTITY(1,1) PRIMARY KEY,
VoteReference UNIQUEIDENTIFIER NULL,
Title VARCHAR(500) NOT NULL,
[Description] VARCHAR(1000) NOT NULL,
ValidFrom DATETIME NOT NULL,
ValidTo DATETIME NOT NULL
)
CREATE TABLE dbo.VoteOption
(
Id INT IDENTITY(1,1) PRIMARY KEY,
Vote_Id INT NOT NULL,
[Description] VARCHAR(500) NOT NULL,
LongDescription VARCHAR(5000) NOT NULL
)
Implementation code is:
public void Save()
{
var vote = new Vote
{
VoteReference = new Guid(),
Title = "Events Vote",
Description = "Which event would you like to see next?",
ValidFrom = DateTime.Now.AddDays(-2),
ValidTo = DateTime.Now.AddDays(3)
};
var options = new List<VoteOption>
{
new VoteOption {Description = "What you want?", LongDescription = "Tell me all about it..."},
new VoteOption {Description = "Another option?", LongDescription = "Tell me some more..."}
};
vote.AddOptions(options);
using (var session = sessionFactory().OpenSession())
{
using (var transaction = session.BeginTransaction())
{
//This works - but undermines cascade!
//foreach (var voteOption in vote.VoteOptions)
//{
// session.Save(voteOption);
//}
session.Save(vote);
transaction.Commit();
}
}
}
private ISessionFactory sessionFactory()
{
var config = new Configuration().Configure();
return Fluently.Configure(config)
.Mappings(m => m.AutoMappings.Add(AutoMap.AssemblyOf<Vote>()))
.BuildSessionFactory();
}
I would say, that setting as shown above (the fluent mapping) is ok. Other words, the code I see right now, seems to be having different issue, then the Exception at the top.
The HasMany cascade setting is OK, but I would suggest to mark it as inverse (see here for more info ... NHibernate will not try to insert or update the properties defined by this join...)
HasMany(x => x.VoteOptions)
.KeyColumn("Vote_Id")
.Inverse()
.Cascade.All();
Also, the Reference should be in most case without Cascade: References(x => x.Vote).Column("Vote_Id");
Having this, and running your code we should be recieving at the moment the SqlException: *Cannot insert the value NULL into column 'Vote_Id'*
Because of the TABLE dbo.VoteOption defintion:
...
Vote_Id INT NOT NULL, // must be filled even on a first INSERT
So, the most important change should be in the place, where we add the voteOption into Vote collection (VoteOptions). We always should/must be providing the reference back, ie. voteOption.Vote = this;
public virtual void AddOption(VoteOption voteOption)
{
VoteOptions.Add(voteOption);
voteOption.Vote = this; // here we should/MUST reference back
}
public virtual void AddOptions(List<VoteOption> options)
{
foreach (var option in options.Where(option => VoteOptionAlreadyExists(option) == false))
{
VoteOptions.Add(option);
option.Vote = this; // here we should/MUST reference back
}
}
After these adjustments, it should be working ok
The cascade option can be set globally using Fluent NHibernate Automapping conventions. The issue that #Radim Köhler pointed out also needs to be corrected when adding items to the List.
Using global conventions:
Add a convention, it can be system wide, or more restricted.
DefaultCascade.All()
Code example:
var cfg = new StoreConfiguration();
var sessionFactory = Fluently.Configure()
.Database(/* database config */)
.Mappings(m =>
m.AutoMappings.Add(
AutoMap.AssemblyOf<Product>(cfg)
.Conventions.Setup(c =>
{
c.Add(DefaultCascade.All());
}
)
.BuildSessionFactory();
Now it will automap the cascade when saving.
More info
Wiki for Automapping
Table.Is(x => x.EntityType.Name + "Table")
PrimaryKey.Name.Is(x => "ID")
AutoImport.Never()
DefaultAccess.Field()
DefaultCascade.All()
DefaultLazy.Always()
DynamicInsert.AlwaysTrue()
DynamicUpdate.AlwaysTrue()
OptimisticLock.Is(x => x.Dirty())
Cache.Is(x => x.AsReadOnly())
ForeignKey.EndsWith("ID")
See more about Fluent NHibernate automapping conventions

How do I return only one specific item from hasmany relationship

Though I have benefited from the collective wisdom of this site many times, this is my first question here.
I have, say, three classes like so:
public class ClassA
{
public ClassA() {}
public virtual IList<ClassB> ClassBs { get; set; }
}
public class ClassB
{
public ClassB() {}
public virtual DateTime StartDate { get; set; }
public virtual DateTime? EndDate { get; set; }
public virtual ClassC SomeObject { get; set; }
}
public class ClassC
{
public ClassC() {}
public virtual string Name { get; set; }
}
I am using NHibernate (3.3.1.4) and FluentNHibernate (1.3.0.733), and the mapping files are:
public Class ClassAMap : ClassMap<ClassA>
{
HasMany(x => x.ClassBs);
}
public Class ClassBMap : ClassMap<ClassB>
{
Map(x => x.StartDate).Not.Nullable();
Map(x => x.EndDate).Nullable();
References(x => x.SomeData).Not.Nullable();
}
public Class ClassCMap : ClassMap<ClassC>
{
Map(x => x.Name).Not.Nullable();
}
There are also IDs and versioning but I somehow think they are irrelevant.
What I want to do is:
select all "SomeObject"s from ClassBs of all "ClassA"s which have their "EndDate"s null and which have the most current StartDate in their group.
I tried some juggling with QueryOver but the most I could get was an IList<IList<ClassB>> which is really far from what I want to accomplish.
Edit:
(I think) Following code accomplishes the task with Linq. But this code requires all records from the DB. This may not be a problem for ClassBs with up to 3 records or so per ClassA but for ClassBs with hundreds of records this means getting all those records from DB to use just one record from ClassBs of each ClassA in the DB.
IList<ClassC> classCLs = new List<ClassC>();
ClassB latest = null;
foreach (
IList<ClassB> classBLs in
Session.QueryOver<ClassA>()
.Select(c => c.ClassBs).List<IList<ClassB>>()
) {
latest = classBLs.Where(cB => cB.EndDate == null).Aggregate((curr, next) => next.StartDate > curr.StartDate ? next : curr);
if (latest != null && !classCLs.Contains(latest.SomeObject)) {
classCLs.Add(latest.SomeObject);
}
}
Assuming your ClassA has an Id property, maybe this, involving a subquery, can be of some help :
ClassA aAlias = null;
ClassB bAliasMax = null, bAlias = null;
var subQuery = QueryOver.Of<ClassA>().JoinAlias(a => a.ClassBs, () => bAliasMax)
.Where(Restrictions.On(() => bAliasMax.EndDate).IsNotNull)
.Where(a=>a.Id==aAlias.Id)
.Select(Projections.Max(() => bAliasMax.StartDate));
var result =
_laSession.QueryOver(() => aAlias)
.JoinAlias(a => a.ClassBs, () => bAlias)
.WithSubquery.WhereProperty(() => bAlias.StartDate).Eq(subQuery)
.Select(Projections.Property(() => bAlias.SomeObject)) // suggested adding from question's author
.List<ClassC>(); // see above
I guess there are more effective answers, which would involve grouping.

Fluent NHibernate compositeid to mapped class

I'm trying to figure out how to use CompositeId to map another class. Here's a test case:
The tables:
TestParent:
TestParentId (PK)
FavoriteColor
TestChild:
TestParentId (PK)
ChildName (PK)
Age
The classes in C#:
public class TestParent
{
public TestParent()
{
TestChildList = new List<TestChild>();
}
public virtual int TestParentId { get; set; }
public virtual string FavoriteColor { get; set; }
public virtual IList<TestChild> TestChildList { get; set; }
}
public class TestChild
{
public virtual TestParent Parent { get; set; }
public virtual string ChildName { get; set; }
public virtual int Age { get; set; }
public override int GetHashCode()
{
return Parent.GetHashCode() ^ ChildName.GetHashCode();
}
public override bool Equals(object obj)
{
if (obj is TestChild)
{
var toCompare = obj as TestChild;
return this.GetHashCode() != toCompare.GetHashCode();
}
return false;
}
}
The Fluent NHibernate maps:
public class TestParentMap : ClassMap<TestParent>
{
public TestParentMap()
{
Table("TestParent");
Id(x => x.TestParentId).Column("TestParentId").GeneratedBy.Native();
Map(x => x.FavoriteColor);
HasMany(x => x.TestChildList).KeyColumn("TestParentId").Inverse().Cascade.None();
}
}
public class TestChildMap : ClassMap<TestChild>
{
public TestChildMap()
{
Table("TestChild");
CompositeId()
.KeyProperty(x => x.ChildName, "ChildName")
.KeyReference(x => x.Parent, "TestParentId");
Map(x => x.Age);
References(x => x.Parent, "TestParentId"); /** breaks insert **/
}
}
When I try to add a new record, I get this error:
System.ArgumentOutOfRangeException :
Index was out of range. Must be
non-negative and less than the size of
the collection. Parameter name: index
I know this error is due to the TestParentId column being mapped in the CompositeId and References calls. However, removing the References call causes another error when querying TestChild based on the TestParentId.
Here's the code that does the queries:
var session = _sessionBuilder.GetSession();
using (var tx = session.BeginTransaction())
{
// create parent
var p = new TestParent() { FavoriteColor = "Red" };
session.Save(p);
// creat child
var c = new TestChild()
{
ChildName = "First child",
Parent = p,
Age = 4
};
session.Save(c); // breaks with References call in TestChildMap
tx.Commit();
}
// breaks without the References call in TestChildMap
var children = _sessionBuilder.GetSession().CreateCriteria<TestChild>()
.CreateAlias("Parent", "p")
.Add(Restrictions.Eq("p.TestParentId", 1))
.List<TestChild>();
Any ideas on how to create a composite key for this scenario?
I found a better solution that will allow querying and inserting. The key is updating the map for TestChild to not insert records. The new map is:
public class TestChildMap : ClassMap<TestChild>
{
public TestChildMap()
{
Table("TestChild");
CompositeId()
.KeyProperty(x => x.ChildName, "ChildName")
.KeyReference(x => x.Parent, "TestParentId");
Map(x => x.Age);
References(x => x.Parent, "TestParentId")
.Not.Insert(); // will avoid "Index was out of range" error on insert
}
}
Any reason you can't modify your query to just be
_sessionBuilder.GetSession().CreateCriteria<TestChild>()
.Add(Restrictions.Eq("Parent.TestParentId", 1))
.List<TestChild>()
Then get rid of the reference?

NHibernate query is not returning any results

I'm trying to get Fluent and NHibernate configured properly with ASP.NET MVC. As far as I know it's configured properly but when I access the page that uses this setup I am not receiving any data results.
The model I'm using is called Brand and the database table is Brands.
Here is a snippet from my BrandController:
public ActionResult Index()
{
IRepository<Brand> repo = new BrandRepository();
var brands = repo.GetAll().ToList<Brand>();
return View(brands);
}
Here is a snippet from my BrandRepository:
ICollection<Brand> IRepository<Brand>.GetAll()
{
using (ISession session = NHibernateHelper.OpenSession())
{
var brands = session
.CreateCriteria(typeof(Brand))
.List<Brand>();
return brands;
}
}
Here is my NHibernateHelper:
public class NHibernateHelper
{
private static ISessionFactory _sessionFactory;
private static ISessionFactory SessionFactory
{
get
{
if (_sessionFactory == null)
{
_sessionFactory = Fluently.Configure()
.Database(
MsSqlConfiguration.MsSql2008
.ConnectionString(c => c
.FromConnectionStringWithKey("ShoesFullAccess")
)
)
.BuildSessionFactory();
}
return _sessionFactory;
}
}
public static ISession OpenSession()
{
return SessionFactory.OpenSession();
}
}
Here is my Brand model:
public class Brand
{
public int Id { get; private set; }
public virtual string Name { get; set; }
public virtual IList<Style> Styles { get; private set; }
public Brand()
{
Styles = new List<Style>();
}
public virtual void AddStyle(Style style)
{
Styles.Add(style);
}
}
And finally, here is my BrandMap:
public class BrandMap : ClassMap<Brand>
{
public BrandMap()
{
Id(x => x.Id);
Map(x => x.Name);
HasMany(x => x.Styles)
.Inverse()
.Cascade.All();
}
}
If anyone could point me in the right direction for how I can narrow down the problem I would very grateful!
You are not adding the mappings to the configuration.
Add this before .BuildSessionFactory():
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<BrandMap>())
I needed to make a few modifications to my code. The first and main one was adding the mappings to the configuration as Diego pointed out.
You are not adding the mappings to the configuration.
Add this before .BuildSessionFactory():
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<BrandMap>())
The next thing I had to fix was my Brand model. I needed to make the Id field virtual.
And finally, I needed to change my BrandMap just a little bit -- I had to specify exactly which table it was pointing to by adding this code Table("Brands");