I read this article on ravendb set operations, but it didn't show me exactly how to update a set of documents via C#. I would like to update a field on all documents that match a certain criteria. Or to put it another way, I would like to take this C# and make it more efficient:
var session = db.GetSession();
foreach(var data in session.Query<Data>().Where(d => d.Color == "Red"))
{
data.Color = "Green";
session.Store(data);
}
session.SaveChanges();
See http://ravendb.net/docs/2.5/faq/denormalized-updates
First parameter is the name of the index you wish to update.
Second parameter is the index query which lets you specify your where clause. The syntax for the query is the lucene syntax (http://lucene.apache.org/java/2_4_0/queryparsersyntax.html). Third parameter is the update clause. Fourth parameter is if you want stale results.
documentStore.DatabaseCommands.UpdateByIndex("DataByColor",
new IndexQuery
{
Query = "Color:red"
}, new[]
{
new PatchRequest
{
Type = PatchCommandType.Set,
Name = "Color",
Value = "Green"
}
},
allowStale: false);
Related
I have a collection that requires frequent updating. The structure is as follows:
{
ObjectId _Id;
string EntryKey;
string Description;
string SearchTerm;
}
There is a unique index on the EntryKey field. I am creating upsert operations like so:
var filter = filterBuilder.Eq(e => e.EntryKey, model.EntryKey);
var update = updateBuilder.Combine(
updateBuilder.SetOnInsert(e => e._Id, ObjectId.GenerateNewId()),
updateBuilder.SetOnInsert(e => e.EntryKey, model.EntryKey.ToLower()),
updateBuilder.Set(e => e.Description, model.Description),
updateBuilder.Set(e => e.SearchTerm, model.SearchTerm));
var op = new UpdateOneModel<SearchEntry>(filter, update){ IsUpsert = true, Hint = "EntryKey_1" };
bulkList.Add(op);
Using the exact same input data for each test with a fresh mongo instance, the first iteration succeeds and the second one fails with E10000: duplicate key error collection on the EntryKey field
When I remove the unique constraint from the index, duplicated documents are created in the collection (with the exception of _Id)
When I run the command in the shell using the same failed ID, the command succeeds and the document is updated.
db.search.bulkWrite([
{
updateOne: {
"filter": { "EntryKey": "ad544f72-496f-4eee-bf53-ffdda57de824" },
"update": {
$setOnInsert: {
_id: ObjectId(),
"EntryKey": "ad544f72-496f-4eee-bf53-ffdda57de824"
},
$set: {
"Description": "This is a description",
"SearchTerm",: "search"
}
},
"upsert": true
}
}
]);
I expect the documents that match the filter predicate to get updated instead of either throwing a duplicate key error (when the unique index is enforced) or insert essentially duplicate documents.
I see in this question the accepted answer is to separate updates from inserts. If that's the case, then what's the point of upsert if it cannot be used in the manner being tried here?
When the code runs the first time, it will create a document where EntryKey is set to model.EntryKey.ToLower().
In the second run, EntryKey is compared with model.EntryKey. Since it was downcased in the upsert, this will only match if there are no uppercase letters in model.EntryKey.
If there are any, the filter will fail to match, and it will attempt to upsert, but fail after converting to lowercase.
To make that consistent, also downcase in the filter, like
var filter = filterBuilder.Eq(e => e.EntryKey, model.EntryKey.ToLower());
i know it is not complicated but i struggle with it.
I have IList<Material> collection
public class Material
{
public string Number { get; set; }
public decimal? Value { get; set; }
}
materials = new List<Material>();
materials.Add(new Material { Number = 111 });
materials.Add(new Material { Number = 222 });
And i have DbSet<Material> collection
with columns Number and ValueColumn
I need to update IList<Material> Value property based on DbSet<Material> collection but with following conditions
Only one query request into database
The returned data from database has to be limited by Number identifier (do not load whole database table into memory)
I tried following (based on my previous question)
Working solution 1, but download whole table into memory (monitored in sql server profiler).
var result = (
from db_m in db.Material
join m in model.Materials
on db_m.Number.ToString() equals m.Number
select new
{
db_m.Number,
db_m.Value
}
).ToList();
model.Materials.ToList().ForEach(m => m.Value= result.SingleOrDefault(db_m => db_m.Number.ToString() == m.Number).Value);
Working solution 2, but it execute query for each item in the collection.
model.Materials.ToList().ForEach(m => m.Value= db.Material.FirstOrDefault(db_m => db_m.Number.ToString() == m.Number).Value);
Incompletely solution, where i tried to use contains method
// I am trying to get new filtered collection from database, which i will iterate after.
var result = db.Material
.Where(x=>
// here is the reasonable error: cannot convert int into Material class, but i do not know how to solve this.
model.Materials.Contains(x.Number)
)
.Select(material => new Material { Number = material.Number.ToString(), Value = material.Value});
Any idea ? For me it is much easier to execute stored procedure with comma separated id values as a parameter and get the data directly, but i want to master linq too.
I'd do something like this without trying to get too cute :
var numbersToFilterby = model.Materials.Select(m => m.Number).ToArray();
...
var result = from db_m in db.Material where numbersToFilterBy.Contains(db_m.Number) select new { ... }
How to deserialize/serialize a property with JSON string array value and then filter (using where clause) in LINQ inside a lambda expression?
void Main()
{
var regionList = new List<Row>() {
new Row { RegionJsonList = "[\"QLD\",\"NSW\"]" },
new Row { RegionJsonList = "[\"TAZ\",\"SA\"]" },
new Row { RegionJsonList = "[\"QLD\",\"VIC\"]" }
};
var filterRegionList = new List<string>() {
"QLD", "NSW"
};
var queryable = regionList.AsQueryable();
// this is obviously wrong, i just want to find the Row that contains one on filterRegionList
var result = queryable.Where(r => JsonConvert.DeserializeObject<string[]>(r.RegionJsonList).Contains(filterRegionList));
result.Count().Dump(); // should be 2
}
class Row
{
public string RegionJsonList { get;set; }
}
Following would work:
var result =
filterRegionList.Aggregate(regionList,(current,filter) =>
current.Where( r => r.RegionJsonList.Contains(filter)).ToList())
Aggregating the filterRegionList and regionList and thus applying filters for the final result. I did not find a requirement to Deserialize the RegionJsonList, since this would work as is, but you may add that part in case you are keen.
Also we are applying And filter via aggregation, it checks for the rows which contains both the filters, and thus provide the result, you may modify filter to achieve more number of rows, like following will select two entries from original regionList
var filterRegionList = new List<string>() { "QLD" };
To filter for rows that contain at least one of the entries from filterRegionList, you can use Enumerable.Intersect and check for non-empty intersections:
var resultAny = queryable.Where(r => JsonConvert.DeserializeObject<string[]>(r.RegionJsonList).Intersect(filterRegionList).Any());
To filter for rows that contain all of the entries from filterRegionList, you can use Enumerable.Except to remove the row's entries from the filter list. If everything gets removed, it's a match:
var resultAll = queryable.Where(r => !filterRegionList.Except(JsonConvert.DeserializeObject<string[]>(r.RegionJsonList)).Any());
(It wasn't entirely clear from your question which you wanted.)
I'm using HtmlAgilityPack to parse a page of HTML and retrieve a number of option elements from a select list.
The GvsaDivisions is a method that returns raw html from the result of a POST, irreverent in the context of the question
public IEnumerable<SelectListItem> Divisions(string season, string gender, string ageGroup)
{
var document = new HtmlDocument();
var html = GvsaDivisions(season);
document.LoadHtml(html);
var options = document.DocumentNode.SelectNodes("//select//option").Select(x => new SelectListItem() { Value = x.GetAttributeValue("value", ""), Text = x.NextSibling.InnerText });
var divisions = options.Where(x => x.Text.Contains(string.Format("{0} {1}", ageGroup, gender)));
if (ageGroup == "U15/U16")
{
ageGroup = "U15/16";
}
if (ageGroup == "U17/U19")
{
ageGroup = "U17/19";
}
return divisions;
}
What I'm observing is this... once the options.Where() is executed, divisions contains a single result. After the test of ageGroup == "U15/U16" and the assignment of ageGroup = "U15/16", divisions now contains 3 results (the original 1, with the addition of 2 new matching the criteria of the new value of ageGroup
Can anybody explain this anomaly? I expected to make a call to Union the result of a new Where query to the original results, but it seems it's happening automagically. While the results are what I desire, I have no way to explain how it's happening (or the certainty that it'll continue to act this way)
LINQ queries use deferred execution, which means they are run whenever you enumerate the result.
When you change a variable that is being used in your query, you actually are changing the result of the next run of the query, which is the next time you iterate the result.
Read more about this here and here:
This is actually by-design, and in many situations it is very useful, and sometimes necessary. But if you need immediate evaluation, you can call the ToList() method at the end of your query, which materializes you query and gives you a normal List<T> object.
The divisions variable contains an unprocessed enumerator that calls the code x.Text.Contains(string.Format("{0} {1}", ageGroup, gender)) on each element in the list of nodes. Since you change ageGroup before you process that enumerator, it uses that new value instead of the old value.
For example, the following code outputs a single line with the text "pear":
List<string> strings = new List<string> { "apple", "orange", "pear", "watermelon" };
string matchString = "orange";
var queryOne = strings.Where(x => x == matchString);
matchString = "pear";
foreach (var item in queryOne)
{
Console.WriteLine(" " + item);
}
I'm thinking along the same lines as Travis, the delayed execution of linq.
I'm not sure if this will avoid the issue, but I generally put my results into an immediate collection like this. With my experience it seems once you shove the results into a real defined collection I believe it may not be delayed execution.
List<SelectListItem> options = document.DocumentNode.SelectNodes("//select//option").Select(x => new SelectListItem() { Value = x.GetAttributeValue("value", ""), Text = x.NextSibling.InnerText }).Where(x => x.Text.Contains(string.Format("{0} {1}", ageGroup, gender))).ToList<SelectListItem>();
I'm using C#, EF5, and Lambda style queries against SQL.
I have the usual scenario of binding data to gridviews. Some of the results for my columns may be too long (character count) and so I only want to display the first 'n' characters. Let's say 10 characters for this example. When I truncate a result, I'd like to indicate this by appending "...". So, let's say the following last names are returned:
Mercer, Smith, Garcia-Jones
I'd like them to be returned like this:
Mercer, Smith, Garcia-Jon...
I was doing something like this:
using (var context = new iaiEntityConnection())
{
var query = context.applications.Where(c => c.id == applicationPrimaryKey);
var results = query.ToList();
foreach (var row in results)
{
if (row.employerName.Length > 10)
{
row.employerName = row.employerName.Substring(0, Math.Min(10, row.employerName.ToString().Length)) + "...";
}
if (row.jobTitle.Length > 10)
{
row.jobTitle = row.jobTitle.Substring(0, Math.Min(10, row.jobTitle.ToString().Length)) + "...";
}
}
gdvWorkHistory.DataSource = results;
gdvWorkHistory.DataBind();
However, if I change my query to select specific columns like this:
var query2 = context.applications.Select(c => new
{
c.id,
c.applicationCode,
c.applicationCategoryLong,
c.applicationType,
c.renew_certification.PGI_nameLast,
c.renew_certification.PGI_nameFirst,
c.renew_certification.PAI_homeCity,
c.renew_certification.PAI_homeState,
c.reviewStatusUser,
c.dateTimeSubmittedByUser
})
The result appears to become read-only if specific columns are selected, and I really should be selecting just the columns I need. I'm losing my ability to edit the result set.
So, I'm rethinking the entire approach. There must be away to select the first 'n' characters on select, right? Is there anyway to append the "..." if the length is > 10 on select? That seems trickier. Also, I guess I could parse through the gridview after bind and make this adjustment. Or, perhaps there is a way to maintain my ability to edit the result set when selecting specific columns?
I welcome your thoughts. Thanks!
To quote MSDN
Anonymous types provide a convenient way to encapsulate a set of read-only properties into a single object without having to explicitly define a type first.
So you would have to define a class and select into that if you want read write capability.
e.g.
public class MyClass {
public int id { get; set; }
public string applicationCode {get; set; }
// rest of property defintions.
}
var query2 = context.applications.Select(c => new MyClass {
id = c.id,
applicationCode = c.applicationCode,
// Rest of assignments
};
As to just providing 10 character limit with ... appended. I'm going to assume you mean on the applicationcategoryLog field but you can use the same logic on other fields.
var query2 = context.applications.Select(c => new
{
c.id,
c.applicationCode,
applicationCategoryLong = (c.applicationCategoryLong ?? string.Empty).Length <= 10 ?
c.applicationCategoryLong :
c.applicationCategoryLong.Substring(0,10) + "...",
c.applicationType,
c.renew_certification.PGI_nameLast,
c.renew_certification.PGI_nameFirst,
c.renew_certification.PAI_homeCity,
c.renew_certification.PAI_homeState,
c.reviewStatusUser,
c.dateTimeSubmittedByUser
})