I have a simple List with dummy data as follows:
List<Organisation> list = new List<Organisation>();
list.Add(new Organisation() { LogoUrl = "/images/logos/Blade.png", OrganisationId = 1, OrganisationName = "Blade" });
list.Add(new Organisation() { LogoUrl = "/images/logos/Torn.png", OrganisationId = 2, OrganisationName = "Torn" });
When I run the linq query:
var results = from org in OrganisationsController.GetDummyList()
where org.OrganisationName.StartsWith(searchString)
select org;
It always returns an Empty result. In this case the searchString is specified by the user and the example would be "Tor".
Using different variations like 'where org.OrganisationName == searchString' where the search string is Torn works. But StartsWith never works.
Any ideas where I'm going wrong?
EDIT:
From Jon's code I changed my code to look as follows:
public JsonResult Search(string searchString)
{
//create json result object
JsonResult data = new JsonResult();
var list = OrganisationsController.GetDummyList();
//query the list
var results = from org in list
where org.OrganisationName.ToLower().Contains(searchString.ToLower())
select org;
if (results.Any())
{
System.Diagnostics.Debug.Write("found");
}
//setup the data
data.Data = results;
//return the data
return Json(data, JsonRequestBehavior.AllowGet);
}
Note: I changed the StartsWith to Contains, but both are giving me similary problems.
One of my organisations is called 'Absa'. Here's the really strange thing when I fire up the app for the first time putting in 'bsa' returns nothing, I then enter 'Absa' and it returns a good result. Then I entered 'bsa' again just to double check and it returned Absa which it didn't in the first test. Why would the result not work at first then work later?
Thanks,
Jacques
Unable to reproduce. It works fine for me:
using System;
using System.Collections.Generic;
using System.Linq;
class Organisation
{
public string LogoUrl { get; set; }
// Removed redundant Organisation prefixes
public int Id { get; set; }
public string Name { get; set; }
}
class Test
{
static void Main()
{
// Used collection initializer for sanity
var list = new List<Organisation>
{
new Organisation { LogoUrl = "Blade.png", Id = 1, Name = "Blade" },
new Organisation { LogoUrl = "Torn.png", Id = 2, Name = "Torn" },
};
string searchString = "Tor";
var query = from org in list
where org.Name.StartsWith(searchString)
select org;
// Nicer version:
// var query = list.Where(org => org.Name.StartsWith(searchString));
Console.WriteLine(query.Count()); // 1
}
}
Work out the difference between your code and my code to find out what's wrong.
In particular, you've shown code using List<T>, which means LINQ to Objects. If your real code uses LINQ to SQL or Entity Framework, that could easily affect things.
Related
I've been trying to get the first matching value from a list of objects within a list of objects that does not exist in a list of strings, but can't seem to find just the right syntax.
public class SO_Response
{
private List<SalesOrderDto> _headers = new List<SalesOrderDto>();
public List<SalesOrderDto> Header
{
get { return _headers; }
set { _headers = value; }
}
}
public class SalesOrderDto
{
public SalesOrderDto(string order, string resultMessage)
{
Order = order;
ResultMessage = resultMessage;
}
public string Order { get; }
public string ResultMessage { get; }
}
Use in method:
var result = new List<IRMS_SO_Response>();
//result = .....
var matches = new HashSet<string>() { "Good Record", "Order is canceled", "status = Cancelled" };
--> Need Linq statement to get first value from result where header.ResultMessage not in matches.
I've tried lots of variations, but never the right one.
var returnMessage = (from x in result
where x.Header.FirstOrDefault(message => !message.ResultMessage.Contains(matches))
I'm not sure that I fully understand what you're looking for. Assuming that you're looking for the first ResultMessage value which is not in the matches set, I believe that the following will do what you want.
var firstNonMatch = result
.SelectMany(t => t.Header.Select(h => h.ResultMessage)) // convert list of responses into list of nested headers
.FirstOrDefault(t => !matches.Contains(t)); // pick the first header that doesn't match (or null if no match)
I am trying to actually replace a collection of Objects of type Game in my Collection "Games".
I want to replace these Objects with entirely new Objects. I have researched a bit on MongoDB and I see that 'UpdateMany' will replace Fields with new values but that's not exactly what I want. I wish to replace the entire Object.
For reference, this is my Game class:
public class Game
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Developer { get; set; }
public int ProjectId { get; set; }
public Game()
{
this.Id = Guid.NewGuid();
}
}
This is my method I am using to attempt a bulk Replace. I am passing in a ProjectId, so for all of the Game Objects that have a ProjectId = to the argument, replace the Object with a new Game Object.
public static void ReplaceGame(int ProjectId, IMongoDatabase Database)
{
IMongoCollection<Game> gameCollection = Database.GetCollection<Game>("Game");
List<Game> gameCollectionBeforeReplacement = gameCollection.Find(g => true).ToList();
if (gameCollectionBeforeReplacement.Count == 0)
{
Console.WriteLine("No Games in Collection...");
return;
}
var filter = Builders<Game>.Filter.Eq(g => g.ProjectId, ProjectId);
foreach (Game game in gameCollection.AsQueryable())
gameCollection.ReplaceOneASync(filter, new Game() { Title = "REPLACEMENT TITLE" });
}
Not only does this take an excessive amount of time. I suspect it's because of the .AsQueryable() call but it also doesn't work. I am wondering how I can actually replace all instances picked up by my filter with new Game Objects.
Consider the following code:
public virtual ReplaceOneResult ReplaceOne(TDocument replacement, int projId)
{
var filter = Builders<TDocument>.Filter.Eq(x => x.ProjectId, projId);
var result = Collection.ReplaceOne(filter, replacement, new UpdateOptions() { IsUpsert = false }, _cancellationToken);
return result;
}
You will find that ReplaceOneResult has a property that tells you the matched count. This makes it possible for you to keep executing the ReplaceOne call until the matched count equals 0. When this happens, you know all documents in your collection that had the corresponding project id have been replaced.
Example:
var result = ReplaceOne(new Game() { Title = "REPLACEMENT TITLE" }, 12);
while (result.MatchedCount > 0)
result = ReplaceOne(new Game() { Title = "REPLACEMENT TITLE" }, 12);
This makes it so that you don't need the call to the database before you start replacing.
However, if you wish to insert the same values for every existing game, I would suggest you to do an UpdateMany operation. There you can use $set to specify all required values. The code above is simply not performant, with going to the database for every single replace call.
Using MongoDB with C# and driver 2.0, I am trying to do the following:
Text search
Sort the hits by text search score
Project BigClass to SmallClass
Here is a (simplified version of) the classes:
class BigClass
{
[BsonIgnoreIfDefault]
public ObjectId _id { get; set; }
public string Guid { get; set; }
public string Title { get; set; }
public DateTime CreationTime { get; set; }
// lots of other stuff
[BsonIgnoreIfNull]
public double? TextMatchScore { get; set; } // Temporary place for the text match score, for sorting
}
class SmallClass
{
[BsonIgnoreIfDefault]
public ObjectId _id { get; set; }
public string Title { get; set; }
[BsonIgnoreIfNull]
public double? TextMatchScore { get; set; } // Temporary place for the text match score, for sorting
}
If I do a text search, it is pretty straightforward:
var F = Builders<BigClass>.Filter.Text("text I am looking for");
var Result = MongoDriver.Find(F).ToListAsync().Result;
If I want to sort by the score of the text search, it's a bit more messy (and very POORLY documented):
var F = Builders<BigClass>.Filter.Text("text I am looking for");
var P = Builders<BigClass>.Projection.MetaTextScore("TextMatchScore");
var S = Builders<BigClass>.Sort.MetaTextScore("TextMatchScore");
var Result = MongoDriver.Find(F).Project<BigClass>.Sort(S).ToListAsync().Result;
Essentially it requires me to add a field in the class (TextMatchScore) to hold the result.
If I want to get the data, without sorting and project it to SmallClass, it is straightforward:
var F = Builders<BigClass>.Filter.Text("text I am looking for");
var P = Builders<BigClass>.Projection.Include(_ => _.id).Include(_ => _.Title);
var Result = MongoDriver.Find(F).Project<SmallClass>(P).ToListAsync().Result;
Now if "I want it all", that's where problem arises:
var F = Builders<BigClass>.Filter.Text("text I am looking for");
var P = Builders<BigClass>.Projection.MetaTextScore("TextMatchScore").Include(_ => _.id).Include(_ => _.Title).Include(_ => _.TextMatchScore);
var S = Builders<BigClass>.Sort.MetaTextScore("TextMatchScore");
var Result = MongoDriver.Find(F).Project<SmallClass>.Sort(S).ToListAsync().Result;
I get an exception:
Message = "QueryFailure flag was true (response was { \"$err\" : \"Can't canonicalize query: BadValue must have $meta projection for all $meta sort keys\", \"code\" : 17287 })."
As expected, the error is not documented anywhere as the Mongo guys expect users to self-document everything.
If I make the projection to 'BigClass', there is no problem, the code runs and just fills in the right fields.
If you google that text with C#, the posts you find are mine when I was trying to figure out the text search, which is also poorly documented.
So when we combine projection, text search and sorting, there doesn't seem to be any example anywhere and I just can't get it to work.
Does anyone know the reason for that problem?
This works for me:
var client = new MongoClient();
var db = client.GetDatabase("test");
var col = db.GetCollection<BigClass>("big");
await db.DropCollectionAsync(col.CollectionNamespace.CollectionName);
await col.Indexes.CreateOneAsync(Builders<BigClass>.IndexKeys.Text(x => x.Title));
await col.InsertManyAsync(new[]
{
new BigClass { Title = "One Jumped Over The Moon" },
new BigClass { Title = "Two went Jumping Over The Sun" }
});
var filter = Builders<BigClass>.Filter.Text("Jump Over");
// don't need to Include(x => x.TextMatchScore) because it's already been included with MetaTextScore.
var projection = Builders<BigClass>.Projection.MetaTextScore("TextMatchScore").Include(x => x._id).Include(x => x.Title);
var sort = Builders<BigClass>.Sort.MetaTextScore("TextMatchScore");
var result = await col.Find(filter).Project<SmallClass>(projection).Sort(sort).ToListAsync();
I removed the include of the TextMatchScore. It still comes back, because it was included by the MetaTextScore("TextMatchScore").
Documentation is a work in progress. We tackle the major use cases first as those hit the most people. This use case isn't that common and hasn't been documented. We certainly accept pull requests, both for code and documentation. Also, feel free to file a documentation ticket at jira.mongodb.org under the CSHARP project.
Solution which works in MongoDB.Driver 2.x is as follows. What is important is to not do Include in Projection, as it will erase default one, (or remember to add proper projection)
Query:
{
"find":"SoceCollection",
"filter":{
"$text":{
"$search":"some text to search"
}
},
"sort":{
"TextScore":{
"$meta":"textScore"
}
},
"projection":{
"TextScore":{
"$meta":"textScore"
},
"_id":0,
"CreatedDate":0
},
"limit":20,
"collation":{
"locale":"en",
"strength":1
} ...
CODE
var sort = Builders<BigModel>.Sort.MetaTextScore(nameof(LightModel.TextScore));
var projection = Builders<BigModel>.Projection
.MetaTextScore(nameof(LightModel.TextScore))
.Exclude(x => x.Id)
.Exclude(x => x.CreatedDate);
return await Collection()
.Find(filter, new FindOptions { Collation = new Collation("en", strength: CollationStrength.Primary) })
.Project<LightModel>(projection)
.Sort(sort)
.Limit(20)
.ToListAsync();
I am trying to figure out the best way to organise a bunch of my data classes, given I need to be able to access some metrics on them all at some point.
Here's a snippet of my OR class:
public enum status { CLOSED, OPEN }
public class OR
{
public string reference { get; set; }
public string title { get; set; }
public status status { get; set; }
}
Not every OR I initialise will have values for all properties. I want to be able to 'collect' thousands of these together in such a way that I can easily obtain a count of how many OR objects had a value set. For example:
OR a = new OR() { reference = "a" }
OR b = new OR() { reference = "b", title = "test" }
OR c = new OR() { reference = "c", title = "test", status = status.CLOSED }
Now these are somehow collected in such a way I can do (pseudo):
int titleCount = ORCollection.titleCount;
titleCount = 2
I would also want to be able gather metrics for the enum type properties, for example retrieve a Dictionary from the collection that looks like:
Dictionary<string, int> statusCounts = { "CLOSED", 1 }
The reason for wanting access to these metrics is that I am building two collections of ORs and comparing them side-by-side for any differences (they should be identical). I want to be able to compare their metrics at this higher level first, then break-down where precisely they differ.
Thanks for any light that can be shed on how to accomplish this. :-)
... to 'collect' thousands of these
Thousands is not a huge number. Just use a List<OR> and you can get all your metrics with Linq queries.
For example:
List<OR> orList = ...;
int titleCount = orList
.Where(o => ! string.IsNullOrEmpty(o.title))
.Count();
Dictionary<status, int> statusCounts = orList
.GroupBy(o => o.status)
.ToDictionary(g => g.Key, g => g.Count());
The existing answers using Linq are absolutely great and really elegant, so the idea presented below is just for posterity.
Here is a (very rough) reflection-based program that will alow you to count the "valid" properties in any collection of objects.
The validators are defined by you in the Validators dictionary so that you can easily change what is a valid/invalid value for each property. You may find it useful as a concept if you end up with objects having tons of properties and don't want to have to write inline linq metrics on the actual collection itself for every single property.
You could weaponise this as a function and then run it against both collections, giving you a basis to report on the exact differences between both since it records the references to the individual objects in the final dictionary.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
namespace reftest1
{
public enum status { CLOSED, OPEN }
public class OR
{
public string reference { get; set; }
public string title { get; set; }
public status status { get; set; }
public int foo { get; set; }
}
//creates a dictionary by property of objects whereby that property is a valid value
class Program
{
//create dictionary containing what constitues an invalid value here
static Dictionary<string,Func<object,bool>> Validators = new Dictionary<string, Func<object,bool>>
{
{"reference",
(r)=> { if (r ==null) return false;
return !String.IsNullOrEmpty(r.ToString());}
},
{"title",
(t)=> { if (t ==null) return false;
return !String.IsNullOrEmpty(t.ToString());}
},
{"status", (s) =>
{
if (s == null) return false;
return !String.IsNullOrEmpty(s.ToString());
}},
{"foo",
(f) =>{if (f == null) return false;
return !(Convert.ToInt32(f.ToString()) == 0);}
}
};
static void Main(string[] args)
{
var collection = new List<OR>();
collection.Add(new OR() {reference = "a",foo=1,});
collection.Add(new OR(){reference = "b", title = "test"});
collection.Add(new OR(){reference = "c", title = "test", status = status.CLOSED});
Type T = typeof (OR);
var PropertyMetrics = new Dictionary<string, List<OR>>();
foreach (var pi in GetProperties(T))
{
PropertyMetrics.Add(pi.Name,new List<OR>());
foreach (var item in collection)
{
//execute validator if defined
if (Validators.ContainsKey(pi.Name))
{
//get actual property value and compare to valid value
var value = pi.GetValue(item, null);
//if the value is valid, record the object into the dictionary
if (Validators[pi.Name](value))
{
var lookup = PropertyMetrics[pi.Name];
lookup.Add(item);
}
}//end trygetvalue
}
}//end foreach pi
foreach (var metric in PropertyMetrics)
{
Console.WriteLine("Property '{0}' is set in {1} objects in collection",metric.Key,metric.Value.Count);
}
Console.ReadLine();
}
private static List<PropertyInfo> GetProperties(Type T)
{
return T.GetProperties(BindingFlags.Public | BindingFlags.Instance).ToList();
}
}
}
You can get the title count using this linq query:
int titleCount = ORCollection
.Where(x => !string.IsNullOrWhiteSpace(x.title))
.Count();
You could get the count of closed like this:
int closedCount = ORCollection
.Where(x => x.status == status.CLOSED)
.Count();
If you were going to have larger collections or you access the values a lot it might be worth creating a custom collection implementation that stores the field counts, it could then increment/decrement these values as you add and remove items. You could also store a dictionary of status counts in this custom collection that gets updated as you add and remove items.
I have a function that (via ajax) I pass a Guid and a comma delimited string of the types of objects I would like to return . I'm having trouble building a link statement that only returns the desired types. I'm struggling with how to build the query to check if string[] relatedTypes matches rw.GetType().Name. Or perhaps there's a better way.
Here's the Model...
public abstract class WebObject : IValidatableObject
{
public WebObject()
{
this.Id = Guid.NewGuid();
RelatedTags = new List<Tag>();
RelatedWebObjects = new List<WebObject>();
}
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public Guid Id { get; set; }
public virtual ICollection<WebObject> RelatedWebObjects { get; set; }
public IList<Guid> RelatedWebObjectIds { get; set; }
}
And here's my function
public JsonResult GetRelatedWebObjectsByWebObject(Guid id, string relatedWebObjectTypes)
{
JsonResult result = new JsonResult();
Guid webSiteId = db.WebObjects.Find(id).WebSiteId;
string[] relatedTypes = relatedWebObjectTypes.Split(',');
var resultData = (from w in db.WebObjects
where w.Id == id
from rw in w.RelatedWebObjects
where rw.GetType().Name.Contains(relatedTypes)
select rw.Id).ToList();
result.Data = resultData;
result.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
return result;
}
Are you looking for something like:
var relatedTypes = new HashSet<string>(relatedWebObjectTypes);
var resultData = (from w in db.WebObjects
where w.Id == id
&& relatedTypes.SetEquals
(w.RelatedWebObjects.Select(rwo => rwo.GetType().Name))
select w.RelatedWebObjectIds).ToList();
Although I would say that it isn't good practice to use a collection of simple type names in this manner. Are you sure you couldn't use a Type[] or similar here?
It's not clear from your question what exactly do you want, but I think it's this:
from w in db.WebObjects
where w.Id == id
from rw in w.RelatedWebObjects
where relatedWebObjectTypes.Contains(rw.GetType().Name)
select rw.Id
This selects all the items from WebObjects with the correct Id (I guess there should be only one, but it does not matter to the query). And for each of them, get the RelatedWebObjects whose type's name is in relatedWebObjectTypes. And for each of those, get their Id.
You would need to refactor a bit, instead of passing in the name of the types as string, you should pass the actual type then use the linq operator for OfType(Of relatedType)
The MSDN Article gives a simple example that should have you on your way.
A little late, but here's what I ended up going with...
public JsonResult GetRelatedWebObjectsByWebObject(Guid id, string relatedWebObjectTypes)
{
JsonResult result = new JsonResult();
Guid webSiteId = db.WebObjects.Find(id).WebSiteId;
List<string> relatedTypes = new List<string>(relatedWebObjectTypes.Split(','));
var resultData = (from w in db.WebObjects
where w.Id == id
from rw in w.RelatedWebObjects
select rw).ToList();
result.Data = resultData.Where(w => relatedTypes.Contains(w.GetType().BaseType.Name) == true).Select(w => new { Id = w.Id, Type = w.GetType().BaseType.Name }).ToList();//w.Id).Select(w => w.GetType().BaseType.Name).ToList();
result.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
return result;
}