我在两个对象(Application
,Query
)之间有一个many-to-many
关系。我已经构建了在两个对象映射中都具有HasManyToMany映射的映射。
我第一次使用SaveOrUpdate应用程序时,它运行良好,并且条目正确地放置在联接表中。
然而,第二次使用它时,我得到了错误:"具有相同标识符的不同对象已经与会话关联"。
异常在下面显示的addQuery代码的最后几行中抛出。
这是实体和地图。如有任何建议,我们将不胜感激。
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();
}
}
}
}
}
更新代码以防帮助他人
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();
}
}
}
}
这里的问题隐藏在许多为打开的会话中(事实上)一个操作。这不是执行此插入/更新的正确方式。我们应该始终将一组相互依赖的操作包装成一个会话,单个事务。
所以发生的事情是,在这里,我们收到了这样的Query
:
Query qry = getQuery(appname, qryname);
我们有一个对象,它是会话的(曾经)部分,刚刚超出范围。qry
实例现在已完全填充,因为
- 它是一个现有的对象(从DB加载)
- 集合
IList<Application> applicationsUsedIn
的映射(请参阅映射)为.Not.LazyLoad()
- 对于其他CCD_ 8映射也是如此
因此,一旦我们进入最后一个事务(以及它自己的会话)。。。我们的物体可能会被深深地占据。。。具有与上次会话内加载的对象相同的ID
快速解决问题:
- 在操作开始时打开会话
- 在开始时打开交易
- 如果确实有新对象,则仅调用
Save()
- 对于通过
getQuery()
检索的对象,getApplication()
(在同一会话中)不要调用SaveOrUpdate()
。它们已经在会话中,这就是SaveOrUpdated在本例中的主要作用(将它们放入会话中) - a) 调用交易。Commit(),所有内容都将正确持久化
b)在操作结束时关闭会话
注意:我会更改您的多对多的映射
- 删除Not.LazyLoad()。懒惰是我们最想要的
- 删除Cascade,因为它与另一端有关,而与配对表无关如果这是故意的,那就别提了