In Linqtosql how do I show items from multiple rows in a single field.
eg I have a 3 table setup for tagging(entity, tag, entitytag) all linked via foreign keys.
For each entity I would like to return the name in one field and then all relevant tags in 2nd field.
eg Item1, tag1; tag2; tag3
Item2, tag4, tag5....
VB statements preferred.
Thanks
Geoff
Okay, not sure if this is the most efficient way but it works.
Dim dc As New DataContext
Dim query = From i In dc.Items _
Let tags = (From t In dc.ItemTags _
Where t.ItemID = i.ID _
Select t.Tag.Name).ToArray _
Select i.ItemName, Tags = String.Join(" | ", tags)
With this answer I am assuming you have your tables setup similar to the following, names are not great, just for illustration:
AnEntity: Id, Name
ATag: Id, TagName
EntityTag: EntityId (FK to AnEntity.Id), TagId (FK to ATag.Id)
You might try this:
var entityTags = from ent in theEntities
from enttags in ent.EntityTags
group enttags by enttags.AnEntity into entityGroup
select new { TheEntity = entityGroup.Key, TheTags =
from t in entityGroup
select t.ATag.TagName };
I have not been able to actually test this, I'll give it a shot this afternoon and edit it if need be. What is happening here is a SelectMany. The 'from ent in dc.AnEntities' gets all of the entity records, then the next 'from enttags in ent.EntityTags' gets all the entity tag records for each entity. The group by does pretty much that, groups the EntityTag records by AnEntity. Put them in an anonymous type and you are good to go.
EDITED:
Okay, changed the code above, it works now. Before you would get a list of the EntityTag objects, now you get the Entiy object and a list of strings (tags) for that entity.
Related
I'm working on developing a category roll-up report for a Magento (1.6) store.
To that end, I want to get an Order Item collection for a subset of products - those product whose unique category id (that's a Magento product attribute that I created) match a particular value.
I can get the relevant result set by basing the collection on catalog/product.
$collection = Mage::getModel('catalog/product')
->getCollection()
->addAttributeToFilter('unique_category_id', '75')
->joinTable('sales/order_item', 'product_id=entity_id', array('price'=>'price','qty_ordered' => 'qty_ordered'));
Magento doesn't like it, since there are duplicate entries for the same product id.
How do I craft the code to get this result set based on Order Items? Joining in the product collection filtered by an attribute is eluding me. This code isn't doing the trick, since it assumes that attribute is on the Order Item, and not the Product.
$collection = Mage::getModel('sales/order_item')
->getCollection()
->join('catalog/product', 'entity_id=product_id')
->addAttributeToFilter('unique_category_id', '75');
Any help is appreciated.
The only way to make cross entity selects work cleanly and efficiently is by building the SQL with the collections select object.
$attributeCode = 'unique_category_id';
$alias = $attributeCode.'_table';
$attribute = Mage::getSingleton('eav/config')
->getAttribute(Mage_Catalog_Model_Product::ENTITY, $attributeCode);
$collection = Mage::getResourceModel('sales/order_item_collection');
$select = $collection->getSelect()->join(
array($alias => $attribute->getBackendTable()),
"main_table.product_id = $alias.entity_id AND $alias.attribute_id={$attribute->getId()}",
array($attributeCode => 'value')
)
->where("$alias.value=?", 75);
This works quite well for me. I tend to skip going the full way of joining the eav_entity_type table, then eav_attribute, then the value table etc for performance reasons. Since the attribute_id is entity specific, that is all that is needed.
Depending on the scope of your attribute you might need to add in the store id, too.
I have the following LLBLGen code that retrieves articles by Category. Essentially it is selecting from the article table where the articles are not marked for deletion and joining on the ArticleTopicCategory table to retrieve specific categories (where category = 'string')
ArticleCollection articles = new ArticleCollection();
IPredicateExpression articlesFilter = new PredicateExpression();
articlesFilter.Add(ArticleFields.IsFlaggedForDeletion != true);
PrefetchPath prefetchTopic = new PrefetchPath(EntityType.ArticleEntity);
prefetchTopic.Add(ArticleEntity.PrefetchPathTopic);
prefetchTopic.Add(ArticleEntity.PrefetchPathArticleTopicCategories).SubPath.Add(ArticleTopicCategoryEntity.PrefetchPathTopicCategory);
articles.GetMulti(articlesFilter, prefetchTopic);
I have added another table named SuppressedArticle which is a 1 to many and contains Id, OrganizationId, and ArticleId. The theory is that since articles are syndicated to multiple websites, if "Website A" did not want to publish "Article A" they could suppress it, i.e insert a record into the SuppressedArticle table.
On the article admin screen, i'd like to add a link button to suppress/unsuppress the article, by adding a left join with the two conditions like:
left join SuppressedArticle on (Article.Id = SuppressedArticle.articleId and SuppressedArticle.organizationId='CC177558-85CC-45CC-B4E6-805BDD1EECCC')
I tried adding the multiple join like so, but I cast/conversion error:
"Cannot implicitly convert type 'SD.LLBLGen.Pro.ORMSupportClasses.FieldCompareValuePredicate' to 'SD.LLBLGen.Pro.ORMSupportClasses.IPredicateExpression'. An explicit conversion exists (are you missing a cast?)"
IRelationCollection relations = new RelationCollection();
relations.Add(ArticleEntity.Relations.SuppressedArticleEntityUsingArticleId, JoinHint.Left).CustomFilter = new FieldCompareValuePredicate(SuppressedArticleFields.OrganizationId, ComparisonOperator.Equal, this.CurrentIdentity.OrganizationId);
Any help would be greatly appreciated!
CustomFilter is of type IPredicateExpression, you create a predicate (of type IPredicate) and assign it to that property, which of course doesn't work :)
do:
IRelationCollection relations = new RelationCollection();
relations.Add(ArticleEntity.Relations.SuppressedArticleEntityUsingArticleId, JoinHint.Left)
.CustomFilter = new PredicateExpression(SuppressedArticleFields.OrganizationId == this.CurrentIdentity.OrganizationId);
I have an Article with a Set of Category.
How can I query, using the criteria interface, for all Articles that contain all Categories with a certain Id?
This is not an "in", I need exclusively those who have all necessary categories - and others. Partial matches should not come in there.
Currently my code is failing with this desperate attempt:
var c = session.CreateCriteria<Article>("a");
if (categoryKeys.HasItems())
{
c.CreateAlias("a.Categories", "c");
foreach (var key in categoryKeys)
c.Add(Restrictions.Eq("c", key)); //bogus, I know!
}
Use the "IN" restriction, but supplement to ensure that the number of category matches is equal to the count of all the categories you're looking for to make sure that all the categories are matched and not just a subset.
For an example of what I mean, you might want to take a look at this page, especially the "Intersection" query under the "Toxi solution" heading. Replace "bookmarks" with "articles" and "tags" with "categories" to map that back to your specific problem. Here's the SQL that they show there:
SELECT b.*
FROM tagmap bt, bookmark b, tag t
WHERE bt.tag_id = t.tag_id
AND (t.name IN ('bookmark', 'webservice', 'semweb'))
AND b.id = bt.bookmark_id
GROUP BY b.id
HAVING COUNT( b.id )=3
I believe you can also represent this using a subquery that may be easier to represent with the Criteria API
SELECT Article.Id
FROM Article
INNER JOIN (
SELECT ArticleId, count(*) AS MatchingCategories
FROM ArticleCategoryMap
WHERE CategoryId IN (<list of category ids>)
GROUP BY ArticleId
) subquery ON subquery.ArticleId = EntityTable.Id
WHERE subquery.MatchingCategories = <number of category ids in list>
I'm not 100% sure, but I think query by example may be what you want.
Assuming that Article to Category is a one-to-many relationship and that the Category has a many-to-one property called Article here is a VERY dirty way of doing this (I am really not proud of this but it works)
List<long> catkeys = new List<long>() { 4, 5, 6, 7 };
if (catkeys.Count == 0)
return;
var cr = Session.CreateCriteria<Article>("article")
.CreateCriteria("Categories", "cat0")
.Add(Restrictions.Eq("cat0.Id", catkeys[0]));
if (catkeys.Count > 1)
{
for (int i = 1; i < catkeys.Count; i++)
{
cr = cr.CreateCriteria("Article", "a" + i)
.CreateCriteria("Categories", "cat" + i)
.Add(Restrictions.Eq("cat" + i + ".Id", catkeys[i]));
}
}
var results = cr.List<Article>();
What it does is to re-join the relationship over and over again guaranteeing you the AND between category Ids. It should be very slow query especially if the list of Ids gets big.
I am offering this solution as NOT a recommended way but at least you can have something working while looking for a proper one.
When paging data, I want to not only return 10 results, but I also want to get the total number of items in all the pages.
How can I get the total count AND the results for the page in a single call?
My paged method is:
public IList GetByCategoryId(int categoryId, int firstResult, int maxResults)
{
IList<Article> articles = Session.CreateQuery(
"select a from Article as a join a.Categories c where c.ID = :ID")
.SetInt32("ID", categoryId)
.SetFirstResult(firstResult)
.SetMaxResults(maxResults)
.List<Article>();
return articles;
}
The truth is that you make two calls. But a count(*) call is very, very cheap in most databases and when you do it after the main call sometimes the query cache helps out.
Your counter call will often be a little different too, it doesn't actually need to use the inner joins to make sense. There are a few other little performance tweaks too but most of the time you don't need them.
I believe you actually can do what you ask. You can retieve the count and the page in one go in code but not in one SQL statement. Actually two queries are send to the database but in one round trip and the results are retrieved as an array of 2 elements. One of the elements is the total count as an integer and the second is an IList of your retrieved entities.
There are 2 ways to do that:
MultyQuery
MultiCriteria
Here is a sample taken from the links below:
IList results = s.CreateMultiQuery()
.Add("from Item i where i.Id > :id")
.Add("select count(*) from Item i where i.Id > :id")
.SetInt32("id", 50)
.List();
IList items = (IList)results[0];
long count = (long)((IList)results[1])[0];
Here is more info on how you can do that. It is really straight forward.
http://ayende.com/Blog/archive/2006/12/05/NHibernateMutliQuerySupport.aspx
http://ayende.com/Blog/archive/2007/05/20/NHibernate-Multi-Criteria.aspx
If you read the 2 articles above you will see that there is a performance gain of using this approach that adds value to the anyway more transparent and clear approach of doing paging with MultiQuery and MultiCriteria against the conventional way.
Please note that recent versions of NHibernate support the idea of futures, so you can do.
var items = s.CreateQuery("from Item i where i.Id > :id")
.SetInt32("id", 50)
.Future<Item>();
var count = s.CreateQuery("select count(*) from Item i where i.Id > :id")
.SetInt32("id", 50)
.FutureValue<long>();
This is a much more natural syntax, and it will still result in a single DB query.
You can read more about it here:
http://ayende.com/Blog/archive/2009/04/27/nhibernate-futures.aspx
I'm trying to write a sql query to get all the fields in a given item in Sitecore.
To say I am stuck is putting it mildly.
I'm guessing I have to do some self joining on the fields table, but I'm getting myself in knots.
Anyone have any ideas?
In none of the cases you should ever try to query the Sitecore database yourself. The database changes over time and this would break your code. Rather, use the Item.Fields. This is a collection which contains all the necessary fields. If you want to make sure that all the fields are loaded(really loaded, not lazy loaded), than you can use Item.Fields.ReadAll().
Edit: Also, keep in mind that querying doesn't allow you to construct an Item, so you miss the behavior of default values and do not use the intelligent Sitecore caching at all.
Try to call Sitecore.Context.Item.Fields.ReadAll() before looking up a field.
First Attempt, but does not return all fields
SELECT I2.Name FROM
Items AS I
JOIN UnversionedFields AS UF ON I.ID = UF.ItemId
JOIN VersionedFields AS V ON I.ID = V.ItemId
JOIN SharedFields AS S ON I.ID = S.ItemId
JOIN Items AS I2 ON I2.ID = UF.FieldId OR I2.ID=V.FieldId OR I2.ID = S.FieldId
WHERE I.ID = '110D559F-DEA5-42EA-9C1C-8A5DF7E70EF9'
GROUP BY I2.Name
By calling item.Fields you get the item fields that you have designated in your templates as well as the Sitecore standard fields that exist on all items. Use the code below if you only want the fields that you have defined in your templates. Of course, this assumes your field names do not start with "__"
// Get Fields directly from the Item
List<string> fieldNames = new List<string>();
item.Fields.ReadAll();
FieldCollection fieldCollection = item.Fields;
foreach (Field field in fieldCollection)
{
//Use the following check if you do not want
//the Sitecore Standard Fields
if (!field.Name.StartsWith("__"))
{
fieldNames.Add(field.Name);
}
}