I am trying to find a way to pass in an optional string list to a query. What I am trying to do is filter a list of tags by the relationship between them. For example if c# was selected my program would suggest only tags that appear in documents with a c# tag and then on the selection of the next, say SQL, the tags that are linked to docs for those two tags together would be shown, whittling it down so that the user can get closer and closer to his goal.
At the moment all I have is:
List<Tag> _tags = (from t in Tags
where t.allocateTagDoc.Count > 0
select t).ToList();
This is in a method that would be called repeatedly with the optional args as tags were selected.
I think I have been coming at it arse-backwards. If I make two(or more) queries one for each supplied tag, find the docs where they all appear together and then bring out all the tags that go with them... Or would that be too many hits on the db? Can I do it entirely through an entity context variable and just query the model?
Thanks again for any help!
You can try this.
First collect tag to search in a list of strings .
List<string> tagStrings = new List<string>{"c#", "sql"};
pass this list in your query, check whether it is empty or not, if empty, it will return all the tags, else tags which matches the tagStrings.
var _tags = (from t in Tags
where t.allocateTagDoc.Count > 0
&& (tagStrings.Count ==0 || tagStrings.Contains(t.tagName))
select t).ToList();
You can also try this, Dictionary represents ID of a document with it's tags:
Dictionary<int, string[]> documents =
new Dictionary<int, string[]>();
documents.Add(1, new string[] { "C#", "SQL", "EF" });
documents.Add(2, new string[] { "C#", "Interop" });
documents.Add(3, new string[] { "Javascript", "ASP.NET" });
documents.Add(4, new string[] { });
// returns tags belonging to documents with IDs 1, 2
string[] filterTags = new string[] { "C#" };
var relatedTags = GetRelatedTags(documents, filterTags);
Debug.WriteLine(string.Join(",", relatedTags));
// returns tags belonging to document with ID 1
filterTags = new string[] { "C#", "SQL" };
relatedTags = GetRelatedTags(documents, filterTags);
Debug.WriteLine(string.Join(",", relatedTags));
// returns tags belonging to all documents
// since no filtering tags are specified
filterTags = new string[] { };
relatedTags = GetRelatedTags(documents, filterTags);
Debug.WriteLine(string.Join(",", relatedTags));
public static string[] GetRelatedTags(
Dictionary<int, string[]> documents,
string[] filterTags)
{
var documentsWithFilterTags = documents.Where(o =>
filterTags
.Intersect(o.Value).Count() == filterTags.Length);
string[] relatedTags = new string[0];
foreach (string[] tags in documentsWithFilterTags.Select(o => o.Value))
relatedTags = relatedTags
.Concat(tags)
.Distinct()
.ToArray();
return relatedTags;
}
Thought I would pop back and share my solution which was completely different to what I first had in mind.
First I altered the database a little getting rid of a useless field in the allocateDocumentTag table which enabled me to use the entity framework model much more efficiently by allowing me to leave that table out and access it purely through the relationship between Tag and Document.
When I fill my form the first time I just display all the tags that have a relationship with a document. Using my search filter after that, when a Tag is selected in a checkedListBox the Document id's that are associated with that Tag(s) are returned and are then fed back to fill the used tag listbox.
public static List<Tag> fillUsed(List<int> docIds = null)
{
List<Tag> used = new List<Tag>();
if (docIds == null || docIds.Count() < 1)
{
used = (from t in frmFocus._context.Tags
where t.Documents.Count >= 1
select t).ToList();
}
else
{
used = (from t in frmFocus._context.Tags
where t.Documents.Any(d => docIds.Contains(d.id))
select t).ToList();
}
return used;
}
From there the tags feed into the doc search and vice versa. Hope this can help someone else, if the answer is unclear or you need more code then just leave a comment and I'll try and sort it.
Related
I'm trying to use DotNetZipLib-DevKit-v1.9 in my MVC3 Project to extract the files to a specific folder.
What i want is -- How to add multiple entries in zip.SelectEntries method.
Here is my code in controller action:
public ActionResult ExtractZip(string fileName, HttpPostedFileBase fileData)
{
string zipToUnpack = #"C:\Users\Public\Pictures\Sample Pictures\images.zip";
string unpackDirectory = System.IO.Path.GetTempPath();
using (ZipFile zip1 = ZipFile.Read(zipToUnpack))
{
// here, we extract every entry, but we could extract conditionally
// based on entry name, size, date, checkbox status, etc.
var collections = zip1.SelectEntries("name=*.jpg;*.jpeg;*.png;*.gif;");//This shows `0` items in collections
foreach (var item in collections)
{
item.Extract(unpackDirectory, ExtractExistingFileAction.OverwriteSilently);
}
}
return Json(true);
}
In this line var collections = zip1.SelectEntries("name=*.jpg;*.jpeg;*.png;*.gif;"); if i specify only single extension ,it works fine
ex:
var collections = zip1.SelectEntries("name=*.gif"); this works good
I've also seen SelectEntries method here, but it doesn't help though
How to add multiple entries ?
Finally i could answer my own question.
Inorder to select multiple entries we need to use OR and to select multiple entries use the following code:
var collections = zip1.SelectEntries("(name=*.jpg) OR (name=*.jpeg) OR (name=*.png) OR (name=*.gif)");
foreach (var item in collections)
{
item.Extract(unpackDirectory, ExtractExistingFileAction.OverwriteSilently);
}
I have a cluttery piece of code that I would like to shorten using Linq. It's about the part in the foreach() loop that performs an additional grouping on the result set and builds a nested Dictionary.
Is this possible using a shorter Linq syntax?
var q = from entity in this.Context.Entities
join text in this.Context.Texts on new { ObjectType = 1, ObjectId = entity.EntityId} equals new { ObjectType = text.ObjectType, ObjectId = text.ObjectId}
into texts
select new {entity, texts};
foreach (var result in q)
{
//Can this grouping be performed in the LINQ query above?
var grouped = from tx in result.texts
group tx by tx.Language
into langGroup
select new
{
langGroup.Key,
langGroup
};
//End grouping
var byLanguage = grouped.ToDictionary(x => x.Key, x => x.langGroup.ToDictionary(y => y.PropertyName, y => y.Text));
result.f.Apply(x => x.Texts = byLanguage);
}
return q.Select(x => x.entity);
Sideinfo:
What basically happens is that "texts" for every language and for every property for a certain objecttype (in this case hardcoded 1) are selected and grouped by language. A dictionary of dictionaries is created for every language and then for every property.
Entities have a property called Texts (the dictionary of dictionaries). Apply is a custom extension method which looks like this:
public static T Apply<T>(this T subject, Action<T> action)
{
action(subject);
return subject;
}
isn't this far simpler?
foreach(var entity in Context.Entities)
{
// Create the result dictionary.
entity.Texts = new Dictionary<Language,Dictionary<PropertyName,Text>>();
// loop through each text we want to classify
foreach(var text in Context.Texts.Where(t => t.ObjectType == 1
&& t.ObjectId == entity.ObjectId))
{
var language = text.Language;
var property = text.PropertyName;
// Create the sub-level dictionary, if required
if (!entity.Texts.ContainsKey(language))
entity.Texts[language] = new Dictionary<PropertyName,Text>();
entity.Texts[language][property] = text;
}
}
Sometimes good old foreach loops do the job much better.
Language, PropertyName and Text have no type in your code, so I named my types after the names...
I had the following query using normal linq and it was working great (using anonymous type),
var result = from s in Items
group s by s.StartTime into groupedItems
select new {groupedItems.Key, Items= groupedItems.OrderBy(x => x.Name) };
But using Dynamic Linq I cannot get it to order by within the groupby.
result = Items.GroupBy("StartTime", "it").OrderBy("Name");
It states the Name isn't available. It is worth noting that if I take my OrderBy off, everything works great but items inside each "Key" are not ordered.
This is a good question!
I simulated your situation by creating a class called Item.
public class Item
{
public DateTime StartTime { get; set; }
public string Name { get; set; }
}
and then created a basic list of items to do the groupby.
List<Item> Items = new List<Item>()
{
new Item() { StartTime = DateTime.Today, Name = "item2"},
new Item() { StartTime = DateTime.Today, Name = "item1"},
new Item() { StartTime = DateTime.Today.AddDays(-1), Name = "item3"},
};
Now the big difference in the 2 queries is where the order by is being performed. In the first query, when you perform groupedItems.OrderBy(x => x.Name) its being performed on a IGrouping<DateTime,Item> or a single entry as it iterates through all the groupings.
In the second query, the orderby is being performed after the fact. This means you're doing an orderby on a IEnumerable<IGrouping<DateTime,Item>> because the iterations have already happened.
Since Microsoft was nice they added something to help deal with this for expressions. This overload allows you to specify the item returned as it iterates through the collection. Here's an example of the code:
var expressionResult = Items.GroupBy(x => x.StartTime,
(key, grpItems) => new { key, Items = grpItems.OrderBy(y => y.Name) });
The second part of the GroupBy you can specify a lambda expression that takes a key and a grouping of items under that key and return an entry that you specify, which is the same as you're doing in the original query.
Hope this helps!
I have the following DB:
Posts which have an Id, Tags also with Id, and TagsToPosts table which have TagsToPosts.PostId => Posts.Id and TagsToPosts.TagId => Tags.Id FK relations.
I need to delete multiple items from TagsToPosts in following way.
I'm creating IList<Tag> newTags by parsing a string. Each tag have it's name. I want to delete all TagsToPosts items pointing to single post (TagsToPosts.PostId == mypostid) and which points to Tag with name which not in my newTags.
For instance I have one post with Id = 1, three tags: 1 => "tag1", 2 => "tag2", 3 => "tag3" And ManyToMany relations table TagsToPosts: 1 => 1, 1 => 2, 1 => 3
So all three tags are linked to my post.
After that I'll create a new IList<Tag> newList = new List<Tag>() by parsing a string. newList contains: 0 => "tag1", 0 => "tag2".
Now I want to remove third relation from table TagsToPosts because my new list of tags doesn't contain tag with name "tag3". So I need to find a difference. I know I can find similar items using JOIN but how to find difference?
I want this to happend in one DB query without iterating over each item to delete it.
You can't do this with LINQ-to-SQL.
LINQ-to-SQL is not good for batch operations - it can't do batch inserts, it can't do batch updates, and it can't do batch deletes. Every object in your collection is treated individually. You can do all the operations in one transaction, but there will always be a query for each record.
MSDN
A better option is to write a stored procedure that will do what you want.
Have you looked at the Linq Except operator?
For example:
var toDelete = (from t in TagsToPost
select t).Except(from nt in newList
select nt, new TagComparer());
class TagComparer: IEqualityComparer<TagsToPosts>
{
public bool Equals(TagsToPosts x, TagsToPosts y)
{
return x.Tag.Equals(y.Tag, CompareOptions.Ordinal);
}
}
PLINQO supports batch delete operations without retrieving the entities first.
var delete = from t in TagsToPost
select t).Except(from nt in newList
select nt, new TagComparer())
context.Tags.Delete(delete);
http://plinqo.com
My solution which lets you make deletions determined by a class field:
public static void DeleteByPropertyList<T, R>(List<T> listToDelete, Expression<Func<T, R>> getField, DataContext context) where T : class {
List<List<string>> partitionedDeletes = listToDelete.Select(d => string.Format("'{0}'", getField.Compile()(d).ToString())).ToList().Partition<string>(2000).ToList();
Func<Expression<Func<T, R>>, string> GetFieldName = propertyLambda => ((MemberExpression)propertyLambda.Body).Member.Name;
MetaTable metaTable = context.Mapping.GetTable(typeof(T));
string tableName = string.Format("{0}.{1}", metaTable.Model.DatabaseName, metaTable.TableName);
foreach (List<string> partitionDelete in partitionedDeletes) {
string statement = "delete from {0} where {1} in ({2})";
statement = string.Format(statement, tableName, GetFieldName(getField), string.Join(",", partitionDelete));
context.ExecuteCommand(statement);
}
}
public static IEnumerable<List<T>> Partition<T>(this IList<T> source, int size) {
for (int i = 0; i < Math.Ceiling(source.Count / (double)size); i++)
yield return new List<T>(source.Skip(size * i).Take(size));
}
Usage:
List<OrderItem> deletions = new List<OrderItem>();
// populate deletions
LinqToSqlHelper.DeleteByPropertyList<OrderItem, long>(deletions, oi => oi.OrderItemId, context);
It only works with a single field, but it could be extended to composite fields easily enough.
I have a two related questions.
First:
I'm looking to do a full text search against a custom entity in Dynamics CRM 4.0. Has anyone done this before or know how to do it?
I know that I can build QueryExpressions with the web service and sdk but can I do a full text search with boolean type syntax using this method? As far as I can tell that won't do the trick.
Second:
Does anyone else feel limited with the searching abilities provided with Dynamics CRM 4.0? I know there are some 3rd pary search products out there but I haven't found one I like yet. Any suggestions would be appreciated.
Searching and filtering via the CRM SDK does take some time to get used to. In order to simulate full text search, you need to use nested FilterExpressions as your QueryExpression.Criteria. SDK page for nested filters The hardest part is figuring out how to build the parent child relationships. There's so much boolean logic going on that it's easy to get lost.
I had a requirement to build a "search engine" for one of our custom entities. Using this method for a complex search string ("one AND two OR three") with multiple searchable attributes was ugly. If you're interested though, I can dig it up. While it's not really supported, if you can access the database directly, I would suggest using SQL's full text search capabilities.
--
ok, here you go. I don't think you'll be able to copy paste this and fulfill your needs. my customer was only doing two to three key word searches and they were happy with the results from this. You can see what a pain it is to just do this in a simple search scenario. I basically puked out code until it was 'working'.
private FilterExpression BuildFilterV2(string[] words, string[] seachAttributes)
{
FilterExpression filter = new FilterExpression();
List<FilterExpression> allchildfilters = new List<FilterExpression>();
List<string> andbucket = new List<string>();
List<string> orBucket = new List<string>();
// clean up commas, quotes, etc
words = ScrubWords(words);
int index = 0;
while (index < words.Length)
{
// if current word is 'and' then add the next wrod to the ad bucket
if (words[index].ToLower() == "and")
{
andbucket.Add(words[index + 1]);
index += 2;
}
else
{
if (andbucket.Count > 0)
{
List<FilterExpression> filters = new List<FilterExpression>();
foreach (string s in andbucket)
{
filters.Add(BuildSingleWordFilter(s, seachAttributes));
}
// send existing and bucket to condition builder
FilterExpression childFilter = new FilterExpression();
childFilter.FilterOperator = LogicalOperator.And;
childFilter.Filters = filters.ToArray();
// add to child filter list
allchildfilters.Add(childFilter);
//new 'and' bucket
andbucket = new List<string>();
}
if (index + 1 < words.Length && words[index + 1].ToLower() == "and")
{
andbucket.Add(words[index]);
if (index + 2 <= words.Length)
{
andbucket.Add(words[index + 2]);
}
index += 3;
}
else
{
orBucket.Add(words[index]);
index++;
}
}
}
if (andbucket.Count > 0)
{
List<FilterExpression> filters = new List<FilterExpression>();
foreach (string s in andbucket)
{
filters.Add(BuildSingleWordFilter(s, seachAttributes));
}
// send existing and bucket to condition builder
FilterExpression childFilter = new FilterExpression();
childFilter.FilterOperator = LogicalOperator.And;
childFilter.Filters = filters.ToArray();
// add to child filter list
allchildfilters.Add(childFilter);
//new 'and' bucket
andbucket = new List<string>();
}
if (orBucket.Count > 0)
{
filter.Conditions = BuildConditions(orBucket.ToArray(), seachAttributes);
}
filter.FilterOperator = LogicalOperator.Or;
filter.Filters = allchildfilters.ToArray();
return filter;
}
private FilterExpression BuildSingleWordFilter(string word, string[] seachAttributes)
{
List<ConditionExpression> conditions = new List<ConditionExpression>();
foreach (string attr in seachAttributes)
{
ConditionExpression expr = new ConditionExpression();
expr.AttributeName = attr;
expr.Operator = ConditionOperator.Like;
expr.Values = new string[] { "%" + word + "%" };
conditions.Add(expr);
}
FilterExpression filter = new FilterExpression();
filter.FilterOperator = LogicalOperator.Or;
filter.Conditions = conditions.ToArray();
return filter;
}
private ConditionExpression[] BuildConditions(string[] words, string[] seachAttributes)
{
List<ConditionExpression> conditions = new List<ConditionExpression>();
foreach (string s in words)
{
foreach (string attr in seachAttributes)
{
ConditionExpression expr = new ConditionExpression();
expr.AttributeName = attr;
expr.Operator = ConditionOperator.Like;
expr.Values = new string[] { "%" + s + "%" };
conditions.Add(expr);
}
}
return conditions.ToArray();
}
Hm, that's a pretty interesting scenario...
You could certainly do a 'Like' query, and 'or' together the colums/attribute conditions you want included in the search. This seems to be how CRM does queries from the box above entity lists (and they're plenty fast). It looks like the CRM database has a full-text index, although exactly which columns are used to populate it is a bit foggy to me after a brief peek.
And remember LinqtoCRM for CRM query love (I started the project, sorry about the shameless plug).
Second - I can recommend "Global Search" by Akvelon which provides ability to search in all Custom Entities and attributes and Out of Box entities and attributes. Also they are using FTS for search in the attached documents contents. You can find more details in their official site: http://www.akvelon.com/Products/Dynamics%20CRM%20global%20Search/default.aspx
I would suggest utilizing the Dynamics CRM filtered views provided for you in the database. Then you can utilize all the power of native SQL to do any LIKE's or other logic you need. Plus, the filtered views are security trimmed, so you won't have to worry about users accessing records they do not have permission to.