Say you have a config file with the following settings:
<someNode>
<node>value A</node>
<node>value B</node>
<node>value C</node>
</someNode>
For the life of me, I can't figure out how to get XmlMassUpdate to inject the following:
<someNode>
<node>value 1</node>
<node>value 2</node>
<node>value 3</node>
</someNode>
The result looks like this:
<someNode>
<node>value 1</node>
<node>value B</node>
<node>value C</node>
</someNode>
What I'm shooting for is:
<someNode>
<node>value A</node>
<node>value B</node>
<node>value C</node>
<node>value 1</node>
<node>value 2</node>
<node>value 3</node>
</someNode>
EDIT: I found a temporary solution (see below)... still interested in something better, though.
<someNode>
<node xmu:key="id" id="1">value A</node>
<node xmu:key="id" id="2">value B</node>
<node xmu:key="id" id="3">value C</node>
</someNode>
The answer is to apply a unique attribute to the element and use that as the xmu:key (see EDIT above).
Get community tasks code.
Replace:
if (keyAttribute == null)
{
xpath = nodeToFind.Name;
}
With:
if (keyAttribute == null)
{
xpath = nodeToFind.Name;
if (nodeToFind.ChildNodes.Count == 1 && nodeToFind.FirstChild.NodeType == XmlNodeType.Text)
{
xpath = string.Format("{0}[{1}='{2}']/{1}", parentNode.LocalName, nodeToFind.LocalName,
nodeToFind.InnerText, nodeToFind.LocalName);
parentNode = parentNode.ParentNode;
}
}
In method :
private XmlNode locateTargetNode(XmlNode parentNode, XmlNode nodeToFind, XmlAttribute keyAttribute)
Rebuild.
You're done :)
Related
I started by using PartyListForm in FindParty.xml. This list loads data related to parties, in my case with Supplier role. I added a new column with an ID from mantle.party.PartyIdentification, with specific partyIdTypeEnumId. The result is satisfactory, I have a list of Suppliers, with their names and respective IDs shown. The problem starts in the moment, when I want to let the user search through those IDs. It does not work. This is the definition of the column:
<field name="idValue">
<header-field title="Company ID" show-order-by="true">
<text-find size="30" hide-options="true"/>
</header-field>
<default-field>
<display text="${partyIdentification?.idValue?:'N/a'}" text-map="partyIdentification"/>
</default-field>
</field>
This is where the data (text-map="partyIdentification") comes from:
<row-actions>
<entity-find-one entity-name="mantle.party.PartyDetail" value-field="party"/>
<entity-find-one entity-name="mantle.party.PartyIdentification" value-field="partyIdentification">
<field-map field-name="partyId" from="partyId"/>
<field-map field-name="partyIdTypeEnumId" value="PtidICO"/>
</entity-find-one>
<entity-find-count entity-name="mantle.party.PartyInvoiceDetail" count-field="invCount">
<econdition field-name="partyId" operator="equals" from="partyId"/>
</entity-find-count>
</row-actions>
This is how it looks on the screen
#David's comment:
There is the original search commented out and my attempt:
<!--<service-call name="mantle.party.PartyServices.find#Party" in-map="context + [leadingWildcard:true, orderByField:'^organizationName', roleTypeId:'Supplier', pageSize:7]" out-map="context"/>-->
<service-call name="mantle.party.PartyServicesEnhancements.findEnhanced#Party" in-map="context + [leadingWildcard:true, orderByField:'^organizationName', roleTypeId:'Supplier', pageSize:7]" out-map="context"/>
I made a few changes by adding new components as a copy of existing ones, namely:
new view-entity with entity-name="FindPartyViewEnhanced" in package="mantle.party as copy of "FindPartyView" with these additions:
<member-entity entity-alias="IDNTF" entity-name="PartyIdentification" join-from-alias="PTY">
<key-map field-name="partyId" related="partyId" />
<entity-condition>
<econdition field-name="partyIdTypeEnumId" operator="equals" value="PtidICO"/>
</entity-condition>
</member-entity>
<alias entity-alias="IDNTF" name="idValue" field="idValue"/>
<alias entity-alias="IDNTF" name="partyIdTypeEnumId" field="partyIdTypeEnumId"/>
new service "findEnhanced" noun="Party" type="script" as a copy of find#Party service with new parameter added:
<parameter name="idValue"/>
new findPartyEnhanced.groovy (copy of findParty.groovy) with a single line added:
if (idValue) { ef.condition(ec.entity.conditionFactory.makeCondition("idValue", EntityCondition.LIKE, (leadingWildcard ? "%" : "") + idValue + "%").ignoreCase()) }
and finally, in the row actions of the screen, where the search is situated, this is what I ended up with:
<service-call name="mantle.party.PartyServicesEnhancements.findEnhanced#Party" in-map="context + [leadingWildcard:true, idValue:idValue, orderByField:'organizationName', roleTypeId:'Supplier', pageSize:7]" out-map="context"/>
Most probably, this is not the best solution, but it worked for me. Hopefully, it will help you as well.
I would like to know if there is a way to create a Restriction on a primitive collection of a Model in NHibernate.3.3.3?
Here's the details:
class Parent {
IEnumerable<string> ChildNames { get; set; }
}
I need to search like so:
private DetachedCriteria BuildQuery() {
var inNames = { "Bob", "Sam", "Dan" };
var query = DetachedCriteria.For<Parent>("parent");
query.Add(Restrictions.In("ChildNames", inNames));
return query;
}
I found this old question that says it's not possible, but given the fact that it's old and doesn't have a ton of upvotes, I'd like to confirm before I refactor.
If I can do it and I'm totally botching it, I'll take that help as well!
In this scenario, we can use Projection (something less type-safe, then mapped Property, but more flexible).
Let's expect the mapping like this:
<bag name="ChildNames" inverse="false" lazy="true" table="[dbo].[ChildNames]"
cascade="all"
batch-size="25">
<key column="ParentId" />
<element type="System.String" column="ChildName" />
</bag>
Then we can adjust the Build query method like this:
protected virtual DetachedCriteria BuildQuery()
{
var inNames = new [] { "Bob", "Sam", "Dan" };
// parent query reference
var query = DetachedCriteria.For<Parent>("parent");
// reference to child query
var child = query.CreateCriteria("ChildNames");
// let's project the column name of the Element, e.g. 'Name'
var columnNameProjection = Projections.SqlProjection(
"ChildName as name", null, new IType[] { NHibernateUtil.String }
);
// in clause
child.Add(Restrictions.In(
columnNameProjection, inNames
));
return query;
}
And this is what we will get:
SELECT ...
FROM Parent this_
inner join [dbo].[ChildNames] childNames3_
on this_.ParentId=childNames3_.ParentId
WHERE ChildName in (#p0, #p1, #p2)
...
#p0=N'Bob',#p1=N'Sam',#p2=N'Dan'
The caveat:
While this is in deed working... the ChildName is used without the Alias. That could be pretty tricky to fulfill... so be careful, if there are more columns with the name ChildName in this scenario
I ended up, like many, refactoring the collection into a strong type.
I'm using Fluent nHibernate for my data layer, and I have a class that is mostly populated through nHibernate/LINQ but in a few advanced usages, needs to be populated by a stored procedure.
The problem I have is the class mapping includes a Formula. When I call a nHibernate/LINQ function, the underlying variable is populated as expected; when I call the GetNamedQuery() function it throws an error:
Value cannot be null. Parameter name: fieldname
It's completely logical that for a NamedQuery, the Formula field isn't populated (obviously I want a subquery here rather than a SQL statement run for every single record returned!), but I'd like to be able to populate the Formula value from the stored procedure - or at least the query not to throw an error.
Is this possible?
Map
public class QuoteMap : ClassMap<Quote>
{
public QuoteMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
...
Map(x => x.ResponseCount).Formula("(SELECT COUNT(*) FROM QuoteResponse WHERE QuoteResponse.QuoteId = Id and QuoteResponse.Status = " + (int)QuoteResponseRepository.Status.Live + ")");
}
}
Repository
// Works fine
public ICollection<Quote> GetAllByStatus(Status status)
{
using (ISession session = NHibernateHelper.OpenSession())
{
var quoteQuery = (from quote in session.Query<Quote>()
where quote.Status == (int)status
select quote).ToList();
return quoteQuery;
}
}
// Dies horribly
public ICollection<Quote> GetListPendingByCompany(Guid companyId, Status status)
{
using (ISession session = NHibernateHelper.OpenSession())
return session.GetNamedQuery("QuoteGetListPendingByCompany")
.SetGuid("Company_Id", companyId)
.SetInt32("QuoteStatus", (int)status)
.List<Quote>();
}
SQL
CREATE PROCEDURE [dbo].[QuoteGetListPendingByCompany]
#CompanyId uniqueidentifier,
#QuoteStatus int
AS
BEGIN
SET NOCOUNT ON;
SELECT
Quote.*,
(
SELECT
COUNT(*)
FROM QuoteResponse
WHERE QuoteResponse.QuoteId = Quote.Id
) AS ResponseCount -- Needs to populate what is currently a formula field
FROM Quote
-- ... code removed
END
StoredProcsMap.hbm.xml
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" auto-import="true" assembly="QMP.Data" namespace="QMP.Data.Model">
<sql-query name="QuoteGetListPendingByCompany" callable="true">
<return class="QMP.Data.Model.Quote, QMP.Data" />
<![CDATA[
exec dbo.QuoteGetListPendingByCompany #CompanyId=:Company_Id,#QuoteStatus=:QuoteStatus
]]>
</sql-query>
</hibernate-mapping>
You should not put the formula in the fluent mapping, this because you have it in your namedquery:
Map(x => x.ResponseCount).Formula("(SELECT COUNT(*) FROM QuoteResponse WHERE QuoteResponse.QuoteId = Quote.Id and QuoteResponse.Status = " + (int)QuoteResponseRepository.Status.Live + ")");
Just map the field as your do with any other field and you will be fine:
Map(x => x.ResponseCount);
Ok after your edit I can now see what you are trying to do, I am not sure its possible, you want to ignore the FORMULA column if using a NamedQuery that calls a SP?
What happens if you specify a ResultTransformer?
return session.GetNamedQuery("QuoteGetListPendingByCompany")
.SetGuid("Company_Id", companyId)
.SetInt32("QuoteStatus", (int)status)
.SetResultTransformer(new AliasToBeanResultTransformer(typeof(Quote)))
.List<Quote>();
}
Although I suspect it will still barf.
I can not figure out how to parse the "author" and "fact" tags out of the following XML. If the formatting looks strange here is a link to the XML doc.
<response stat="ok">
−<ltml version="1.1">
−<item id="5403381" type="work">
<author id="21" authorcode="rowlingjk">J. K. Rowling</author>
<url>http://www.librarything.com/work/5403381</url>
−<commonknowledge>
−<fieldList>
−<field type="42" name="alternativetitles" displayName="Alternate titles">
−<versionList>
−<version id="3413291" archived="0" lang="eng">
<date timestamp="1298398701">Tue, 22 Feb 2011 13:18:21 -0500</date>
−<person id="18138">
<name>ablachly</name>
<url>http://www.librarything.com/profile/ablachly</url>
</person>
−<factList>
<fact>Harry Potter and the Sorcerer's Stone </fact>
</factList>
</version>
</versionList>
</field>
So far I have tried this code to get the author but it does not work:
#xml_doc = Nokogiri::XML(open("http://www.librarything.com/services/rest/1.1/?method=librarything.ck.getwork&isbn=0590353403&apikey=d231aa37c9b4f5d304a60a3d0ad1dad4"))
#xml_doc.xpath('//response').each do |n|
#author = n
end
I couldn't get at any nodes deeper than //response using the link you provided. I ended up using Nokogiri::XML::Reader and pushing elements into a hash, since there may be multiple authors, and there are definitely multiple facts. You can use whatever data structure you like, but this gets the content of the fact and author tags:
require 'nokogiri'
require 'open-uri'
url = "http://www.librarything.com/services/rest/1.1/?method=librarything.ck.getwork&isbn=0590353403&apikey=d231aa37c9b4f5d304a60a3d0ad1dad4"
reader = Nokogiri::XML::Reader(open(url))
book = {
author: []
fact: []
}
reader.each do |node|
book.each do |k,v|
if node.name == k.to_s && !node.inner_xml.empty?
book[k] << node.inner_xml
end
end
end
You could try:
nodes = #xml_doc.xpath("//xmlns:author", "xmlns" => "http://www.librarything.com/")
puts nodes[0].inner_text
nodes = #xml_doc.xpath("//xmlns:fact", "xmlns" => "http://www.librarything.com/")
nodes.each do |n|
puts n.inner_text
end
The trick is in the namespace.
In my MSSQL server I have a SQL view called AllFavourite. In order to load the data into my DTO class I have the following in my hbm.xml file...
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="Domain.Model.Entities" assembly="Domain.Model">
<import class="AllFavourite"/>
</hibernate-mapping>
In my code I have the following.
public IList<AllFavourite> GetFavourites(int userId)
{
var query = Session
.CreateSQLQuery("SELECT * FROM AllFavourite where UserId=:UserId")
.SetInt32("UserId", userId)
.SetResultTransformer(new AliasToBeanResultTransformer(typeof(AllFavourite)));
return query.List<AllFavourite>();
}
This works great and produces the results that I am after, however I would like to move the SQL from code into a named query into the hbm.xml file. So my hbm.xml file now looks like this
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="Domain.Model.Entities" assembly="Domain.Model">
<import class="AllFavourite"/>
<query name="GetAllFavouriteByUserId">
<![CDATA[
SELECT * FROM AllFavourite WHERE UserId=:UserId
]]>
</query>
</hibernate-mapping>
and my code now looks like this
public IList<AllFavourite> GetFavourites(int userId)
{
var query = Session
.GetNamedQuery("GetAllFavouriteByUserId")
.SetInt32("UserId", userId)
.SetResultTransformer(new AliasToBeanResultTransformer(typeof(AllFavourite)));
return query.List<AllFavourite>();
}
However when I run this I get an error:-
Parameter UserId does not exist as a
named parameter in [SELECT * FROM
AllFavourite WHERE UserId=:UserId]
So my question is it possible to use a named query in this manner?
The query tag expects a HQL query:
<query name="GetAllFavouriteByUserId">
<![CDATA[
from AllFavourite where UserId = :UserId
]]>
</query>
If you want to write a native sql query you should use the sql-query tag:
<sql-query name="GetAllFavouriteByUserId">
<return alias="foo" class="Foo"/>
<![CDATA[
SELECT {foo.ID} as {foo.ID},
{foo}.NAME AS {foo.Name}
FROM sometable
WHERE {foo}.ID = :UserId
]]>
</sql-query>
Don't you need this?
<query-param name='UserId' type='Integer'/>