I am working on a mvc app using nhibernate as the orm (ncommon framework)
I have parent/child entities: Product, Vendor & ProductVendors and a one to many relationship between them with Product having a ProductVendors collection Product.ProductVendors.
I currently am retrieving a Product object and eager loading the children and sending these down the wire to my asp.net mvc client.
A user will then modify the list of Vendors and post the updated Product back. I am using a custom model binder to generate the modified Product entity. I am able to update the Product fine and insert new ProductVendors.
My problem is that dereferenced ProductVendors are not cascade deleted when specifying Product.ProductVendors.Clear() and calling _productRepository.Save(product).
The problem seems to be with attaching the detached instance. Here are my mapping files:
Product
<?xml version="1.0" encoding="utf-8" ?>
<id name="Id">
<generator class="guid.comb" />
</id>
<version name="LastModified"
unsaved-value="0"
column="LastModified"
/>
<property name="Name" type="String" length="250" />
ProductVendors
<?xml version="1.0" encoding="utf-8" ?>
<id name="Id">
<generator class="guid.comb" />
</id>
<version name="LastModified"
unsaved-value="0"
column="LastModified"
/>
<property name="Price" />
<many-to-one
name="Product"
class="Product"
column="ProductId"
lazy="false"
not-null="true"
/>
<many-to-one
name="Vendor"
class="Vendor"
column="VendorId"
lazy="false"
not-null="true"
/>
Custom Model Binder:
using System;
using Test.Web.Mvc;
using Test.Domain;
namespace Spoked.MVC
{
public class ProductUpdateModelBinder : DefaultModelBinder
{
private readonly ProductSystem ProductSystem;
public ProductUpdateModelBinder(ProductSystem productSystem)
{
ProductSystem = productSystem;
}
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var product = bindingContext.Model as Product;
if (product != null)
{
product.Category = ProductSystem.GetCategory(new Guid(bindingContext.ValueProvider["Category"].AttemptedValue));
product.Brand = ProductSystem.GetBrand(new Guid(bindingContext.ValueProvider["Brand"].AttemptedValue));
product.ProductVendors.Clear();
if (bindingContext.ValueProvider["ProductVendors"] != null)
{
string[] productVendorIds = bindingContext.ValueProvider["ProductVendors"].AttemptedValue.Split(',');
foreach (string id in productVendorIds)
{
product.AddProductVendor(ProductSystem.GetVendor(new Guid(id)), 90m);
}
}
}
}
}
}
Controller:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Update(Product product)
{
using (var scope = new UnitOfWorkScope())
{
//product.ProductVendors.Clear();
_productRepository.Save(product);
scope.Commit();
}
using (new UnitOfWorkScope())
{
IList<Vendor> availableVendors = _productSystem.GetAvailableVendors(product);
productDetailEditViewModel = new ProductDetailEditViewModel(product,
_categoryRepository.Select(x => x).ToList(),
_brandRepository.Select(x => x).ToList(),
availableVendors);
}
return RedirectToAction("Edit", "Products", new {id = product.Id.ToString()});
}
The following test does pass though:
[Test]
[NUnit.Framework.Category("ProductTests")]
public void Can_Delete_Product_Vendors_By_Dereferencing()
{
Product product;
using(UnitOfWorkScope scope = new UnitOfWorkScope())
{
Console.Out.WriteLine("Selecting...");
product = _productRepository.First();
Console.Out.WriteLine("Adding Product Vendor...");
product.AddProductVendor(_vendorRepository.First(), 0m);
scope.Commit();
}
Console.Out.WriteLine("About to delete Product Vendors...");
using (UnitOfWorkScope scope = new UnitOfWorkScope())
{
Console.Out.WriteLine("Clearing Product Vendor...");
_productRepository.Save(product); // seems to be needed to attach entity to the persistance manager
product.ProductVendors.Clear();
scope.Commit();
}
}
Going nuts here as I almost have a very nice solution between mvc, custom model binders and nhibernate. Just not seeing my deletes cascaded. Any help greatly appreciated.
Chev
If you want your child entities were deleted when parent is deleted you need to set
cascade="all-delete-orphan"
option in collection mapping. More about cascading options you can find here
Needed to call the Merge method of the session: ISession.Merge(product)
Related
I am trying to follow some instructions I found online in order to implement output caching for episerver.
In my web.config I have set up the following:
<caching>
<outputCache enableOutputCache="true">
</outputCache>
<outputCacheSettings>
<outputCacheProfiles>
<add name="ClientResourceCache" enabled="true" duration="3600" varyByParam="*" varyByContentEncoding="gzip;deflate" />
</outputCacheProfiles>
</outputCacheSettings>
</caching>
As a test, I selected StartPageController.cs and added the [ContentOutputCache] tag like so:
[ContentOutputCache]
public class StartPageController : PageControllerBase<StartPage>
{
public ActionResult Index(StartPage currentPage)
{
var model = PageViewModel.Create(currentPage);
if (SiteDefinition.Current.StartPage.CompareToIgnoreWorkID(currentPage.ContentLink))
{
var editHints = ViewData.GetEditHints<PageViewModel<StartPage>, StartPage>();
editHints.AddConnection(m => m.Layout.Logotype, p => p.SiteLogotype);
editHints.AddConnection(m => m.Layout.Footer, p => p.FooterBlock);
}
return View(model);
}
}
}
Then the instructions say:
At some point on a page load you need to add the following code:
public void SetResposneHeaders()
{
HttpContext.Current.Response.Cache.SetExpires(DateTime.Now.AddMinutes(2.0));
HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.Public);
HttpContext.Current.Response.Cache.SetValidUntilExpires(true);
}
Due to my limited knowledge of .NET, MVC etc.. this part confuses me as I am not sure in which file to place it and where? Does this go in StartPageController.cs? Or somewhere else?
Any pointers would be appreciated.
The instructions I am trying to follow are here.
I have 10 article content items and each article item has a contributors checklist
I have created a Contributors Facet for faceted search in the editor. But checklist values are indexed as string id's.
Now on search result page the facet values are appearing as string id's.
I have created a ComputedField to index the display name
public class Contributors : IComputedIndexField
{
public object ComputeFieldValue(IIndexable indexable)
{
var item = indexable as SitecoreIndexableItem;
if (item == null || item.Item == null) return string.Empty;
StringBuilder ContributorsNameList = new StringBuilder();
IIndexableDataField cField = indexable.GetFieldByName("Contributors");
if (cField != null)
{
var cList = cField.Value.ToString().Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries).ToList();
foreach (var cId in cList)
{
var cItem = item.Item.Database.GetItem(new ID(cId));
if (cItem != null)
ContributorsNameList.Append(cItem.Name.ToString());
}
return ContributorsNameList;
}
return null;
}
public string FieldName { get; set; }
public string ReturnType { get; set; }
}
and config file
<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<contentSearch>
<configuration>
<defaultIndexConfiguration>
<fields hint="raw:AddComputedIndexField">
<field fieldName="tagsfacet" storageType="yes" indexType="untokenized"> Sandbox.SitecoreCustomizations.ComputedIndexFields.TagsFacet, Sandbox</field>
</fields>
<fields hint="raw:AddFieldByFieldName">
<field fieldName="tagsfacet" storageType="YES" indexType="TOKENIZED" vectorType="NO" boost="1f" type="System.String" settingType="Sitecore.ContentSearch.LuceneProvider.LuceneSearchFieldConfiguration, Sitecore.ContentSearch.LuceneProvider">
<Analyzer type="Sitecore.ContentSearch.LuceneProvider.Analyzers.LowerCaseKeywordAnalyzer, Sitecore.ContentSearch.LuceneProvider" />
</field>
</fields>
</defaultIndexConfiguration>
</configuration>
</contentSearch>
</sitecore>
</configuration>
but now getting both the id's and names(occurring twice)
You need to take a look at BucketConfiguration.ResolveFacetValueToFriendlyName
in Sitecore.Buckets.config.
<!-- RESOLVE FACET VALUE TO FRIENDLY NAME
If you are storing a field in the index that is being faceted on, it may be stored as an ID. This Setting when set to true, will try and resolve this to the friendly item name instead.
USAGE: In an environment with huge amounts of items (e.g. 1 Million), this will not scale properly. -->
<setting name="BucketConfiguration.ResolveFacetValueToFriendlyName" value="false"/>
Patch this value to true and it should work.
This way your ComputedField should become obsolete.
I've got a Grails (2.3.8) project that's integrating with a few Java classes. When I attempt to dataBind a one to many relationship, I receive the following error:
{
"errors":
[
{
"object": "com.sample.Author",
"message": "No such field: referencedPropertyType for class: org.codehaus.groovy.grails.orm.hibernate.GrailsHibernateDomainClassProperty"
}
]
}
Does anyone know why the data binding function is trying to bind "referencePropertyType"?
I've included a simplified version of my project with an Author and Books added to the src/java folder.
Author.java
#Entity
public class Author {
private long _id;
private List<Books> _books;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "author")
public List<Book> getBooks() {
return _books;
}
// getters and setters
}
Book.java
#Entity
public class Book {
private long _id;
private Author author;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "AUTHOR_ID", foreignKey = #ForeignKey(name = "author_fk"), nullable = false)
public Author getAuthor() {
return _author;
}
// getters and setters
}
hibernate.cfg.xml
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<mapping class="com.sample.Author" />
<mapping class="com.sample.Book" />
</session-factory>
</hibernate-configuration>
AuthorController.groovy
class AuthorController {
def doSomething() {
def authorInstance = new Author(params)
if(authorInstance.hasErrors()) {
println authorInstance.errors as JSON
}
}
}
Does anyone know why the data binding function is trying to bind
"referencePropertyType"?
The data binding function is not trying to bind referencedPropertyType. The binder is trying to retrieve that value because the value helps the data binder figure out which types of objects need to be created during data binding.
It looks like there may be a bug with respect to Java domain models in certain circumstances when using the Hibernate plugin. If you file a bug report at https://jira.grails.org/browse/GRAILS and attach a simple sample app, we will get it straightened out.
Sorry for the trouble.
After upgrading my project from 2.3.8 to 2.4.0 the issue has been resolved. Looks like it was a bug, but upgrading the project fixed the issue.
Trying to develop a composite component using jsf2.0 (Mojarra) which should render command buttons dynamically based on the list from the bean. I was able to render the buttons but action is not getting triggered.Could any one please help me to resolve the issue?
Here follows the code
<composite:interface>
<composite:attribute name="buttonList" required="true"
type="java.util.List" />
<composite:attribute name="beanName" required="true"
type="java.lang.Object" />
</composite:interface>
<composite:implementation>
<ui:repeat var="listItem" value="#{cc.attrs.buttonList}">
<h:commandButton value="#{listItem.buttonName}"
action="#{cc.attrs.beanName.listItem.buttonAction}">
</h:commandButton>
</ui:repeat>
</composite:implementation>
This is used as
<utils:buttonGroup buttonList="#{testButtonBean.buttonList}"
beanName="#{testButtonBean}" />
The bean looks like
public class TestButtonBean {
public List<ButtonPOJO> buttonList = new ArrayList<ButtonPOJO>();
public List<ButtonPOJO> getButtonList() {
return buttonList;
}
public void setButtonList(List<ButtonPOJO> buttonList) {
this.buttonList = buttonList;
}
public void preProcess() {
if (null != buttonList && buttonList.size() == 0) {
ButtonPOJO ob1 = new ButtonPOJO("Continue", "next");
ButtonPOJO ob2 = new ButtonPOJO("Back", "prev");
buttonList.add(ob1);
buttonList.add(ob2);
}
}
public String next() {
return "page1";
}
public String prev() {
return "page2";
}
}
action="#{cc.attrs.beanName.listItem.buttonAction}"
This is not right. This syntax is basically looking for a property listItem on beanName and then trying to invoke the literal action buttonAction() on it.
You need the brace notation action="#{bean[methodName]}" if you want to specify the action method name as string coming from another bean property.
action="#{cc.attrs.beanName[listItem.buttonAction]}"
Unrelated to the concrete problem, if the above solution still fails, then that can only mean that the value="#{cc.attrs.buttonList}" has incompatibly changed during the form submit request. You need to make sure that exactly the same list is prepared during the postback as it was during the initial request. See also point 4 of commandButton/commandLink/ajax action/listener method not invoked or input value not updated.
Has anyone managed to get nhibernate.search (Lucene) to work with S#arp Architecture? I think I have it all wired up correctly except Luke shows no records or indexes when I run my indexing method. The index files for the entity are created (segments.gen & segments_1) but both are 1kb in size which explains why Luke shows no data.
I execute no other code specific to getting search to work, am I missing some initialisation calls? I assume the listeners get picked up automatically by nhibernate.
In my Web project I have:
NHibernate.config
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.connection_string">Data Source=.\SQLEXPRESS;Database=MyDatabase;Integrated Security=True;</property>
<property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
<property name="show_sql">true</property>
<property name="generate_statistics">true</property>
<property name="connection.release_mode">auto</property>
<property name="adonet.batch_size">500</property>
<property name="proxyfactory.factory_class">NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</property>
<listener class='NHibernate.Search.Event.FullTextIndexEventListener, NHibernate.Search' type='post-insert'/>
<listener class='NHibernate.Search.Event.FullTextIndexEventListener, NHibernate.Search' type='post-update'/>
<listener class='NHibernate.Search.Event.FullTextIndexEventListener, NHibernate.Search' type='post-delete'/>
</session-factory>
</hibernate-configuration>
Web.Config
<configSections>
...
<section name="nhs-configuration" type="NHibernate.Search.Cfg.ConfigurationSectionHandler, NHibernate.Search" requirePermission="false" />
</configSections>
<nhs-configuration xmlns='urn:nhs-configuration-1.0'>
<search-factory>
<property name="hibernate.search.default.directory_provider">NHibernate.Search.Store.FSDirectoryProvider, NHibernate.Search</property>
<property name="hibernate.search.default.indexBase">~\Lucene</property>
</search-factory>
</nhs-configuration>
My entity is decorated as follows:
[Indexed(Index = "Posting")]
public class Posting : Entity
{
[DocumentId]
public new virtual int Id
{
get { return base.Id; }
protected set { base.Id = value; }
}
[Field(Index.Tokenized, Store = Store.Yes)]
[Analyzer(typeof(StandardAnalyzer))]
public virtual string Title { get; set; }
[Field(Index.Tokenized, Store = Store.Yes)]
[Analyzer(typeof(StandardAnalyzer))]
public virtual string Description { get; set; }
public virtual DateTime CreatedDate { get; set; }
...
}
And I run the following to create the index
public void BuildSearchIndex()
{
FSDirectory directory = null;
IndexWriter writer = null;
var type = typeof(Posting);
var info = new DirectoryInfo(GetIndexDirectory());
if (info.Exists)
{
info.Delete(true);
}
try
{
directory = FSDirectory.GetDirectory(Path.Combine(info.FullName, type.Name), true);
writer = new IndexWriter(directory, new StandardAnalyzer(), true);
}
finally
{
if (directory != null)
{
directory.Close();
}
if (writer != null)
{
writer.Close();
}
}
var fullTextSession = Search.CreateFullTextSession(this.Session);
// select all Posting objects from NHibernate and add them to the Lucene index
foreach (var instance in Session.CreateCriteria(typeof(Posting)).List<Posting>())
{
fullTextSession.Index(instance);
}
}
private static string GetIndexDirectory()
{
var nhsConfigCollection = CfgHelper.LoadConfiguration();
var property = nhsConfigCollection.DefaultConfiguration.Properties["hibernate.search.default.indexBase"];
var fi = new FileInfo(property);
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fi.Name);
}
Found an answer to my question so here it is in case anyone else comes by this problem.
NHS configuration in web.config contained such lines:
<property name="hibernate.search.default.directory_provider">NHibernate.Search.Store.FSDirectoryProvider, NHibernate.Search</property>
<property name="hibernate.search.default.indexBase">~\SearchIndex</property>
First line should be removed because in this case NHS consider it as though index shares. It is known NHibernateSearch issue.
If the site is run from IIS, Network Service should have all permissions on search index directory.
Jordan, are you using the latest bits from NHContrib for NHibernate.Search? I just recently updated my bits and I am running into the same situation you are. It works for me on older bits, from about July. But I can't get my indexes to create either. Your config looks right, same as mine. And your indexing method looks good too.
Jordan, there is now an alternative to attribute based Lucene.NET mapping called FluentNHibernate.Search, this project is hosted on codeplex.
http://fnhsearch.codeplex.com/
public class BookSearchMap : DocumentMap<Book>
{
public BookSearchMap()
{
Id(p => p.BookId).Field("BookId").Bridge().Guid();
Name("Book");
Boost(500);
Analyzer<StandardAnalyzer>();
Map(x => x.Title)
.Analyzer<StandardAnalyzer>()
.Boost(500);
Map(x => x.Description)
.Boost(500)
.Name("Description")
.Store().Yes()
.Index().Tokenized();
}
}