Realm + Xamarin Forms Update Query - c#

I'm using Realm + Xamarin Forms to do what I figure is about the most basic operation possible: a list view shows a collection of items, with a search bar filtering the results as the user types.
I have a get only collection property used as the list view's items source, initially populated from a Realm query, and this gets updated automatically with any changes to data, but I can't figure out how to update the search text without adding a set and literally replacing the entire collection.
This is very inefficient--I assume this is triggering re-registration of a bunch of notify-changed event listeners for the collection and each item in it and generally causing mass chaos with each letter tapped.
In the past I've created my own wrapping observable collection with a search method to handle this and I suppose that is an option here as well, but is there any way to do this with Realm? That is, to update the query without recreating the entire collection, some way to re-run the original query?

Update: This technique not longer works.
https://github.com/realm/realm-dotnet/issues/1569
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
...it also differs from the behavior of LINQ to Objects, where every iteration will reevaluate expressions, meaning that changes to both sides of a condition will affect the result. A Realm query will evaluate the right-hand sides of the conditions on the first run.
When you construct a query that contains Where parameters that are based upon non-Realm based conditions the query results do not update when those variable/parameters are changed unless you update/execute the query again.
Realm queries are live, in the sense that they will continue to represent the current state of the database.
So what I do is to create a filter class (RealmObject), then if you instance a "filter object", save it to Realm, you can base your Linq's Where parameters upon one or more of the "filter" properties. Updating this RealmObject filter via Realm.Add(filterObject, true) your queries based upon that object are also updated
Realm queries are live, in the sense that they will continue to represent the current state of the database.
The results are lighting fast filtering that works great in any UI Search routine.
Example Model:
public class ARealmClass : RealmObject
{
public int Key { get; set; }
public string KeyString { get; set; }
}
public class ARealmClassFilter : RealmObject
{
[PrimaryKey]
public int Key { get; set; }
public int FilterKeyBy { get; set; }
}
Populate a Realm with some test data
var realm = Realm.GetInstance();
var all = realm.All<ARealmClass>();
if (all.Count() == 0)
{
realm.Write(() =>
{
for (int i = 0; i < 1000; i++)
{
var obj = new ARealmClass { Key = i, KeyString = i.ToString() };
realm.Add(obj);
}
});
}
Dynamic Live Query Example:
var realm = Realm.GetInstance();
var all = realm.All<ARealmClass>();
Console.WriteLine(all.Count());
var filterItem = new ARealmClassFilter { Key = 1, FilterKeyBy = 500 };
realm.Write(() =>
{
realm.Add(filterItem);
});
var filtered = all.Where(_ => _.Key > filterItem.FilterKeyBy);
Console.WriteLine(filtered.Count());
realm.Write(() =>
{
filterItem.FilterKeyBy = 750;
realm.Add(filterItem, true);
});
Console.WriteLine(filtered.Count());
Output:
2017-04-24 11:53:20.376 ios_build_foo[24496:3239020] 1000
2017-04-24 11:53:20.423 ios_build_foo[24496:3239020] 499
2017-04-24 11:53:20.425 ios_build_foo[24496:3239020] 249
Note: Quoted text # https://realm.io/docs/xamarin/latest/api/linqsupport.html

Related

How to do this kind of search in ASP.net MVC?

I have an ASP.NET MVC web application.
The SQL table has one column ProdNum and it contains data such as 4892-34-456-2311.
The user needs a form to search the database that includes this field.
The problem is that the user wants to have 4 separate fields in the UI razor view whereas each field should match with the 4 parts of data above between -.
For example ProdNum1, ProdNum2, ProdNum3 and ProdNum4 field should match with 4892, 34, 456, 2311.
Since the entire search form contains many fields including these 4 fields, the search logic is based on a predicate which is inherited from the PredicateBuilder class.
Something like this:
...other field to be filtered
if (!string.IsNullOrEmpty(ProdNum1) {
predicate = predicate.And(
t => t.ProdNum.toString().Split('-')[0].Contains(ProdNum1).ToList();
...other fields to be filtered
But the above code has run-time error:
The LINQ expression node type 'ArrayIndex' is not supported in LINQ to Entities`
Does anybody know how to resolve this issue?
Thanks a lot for all responses, finally, I found an easy way to resolve it.
instead of rebuilding models and change the database tables, I just add extra space in the search strings to match the search criteria. since the data format always is: 4892-34-456-2311, so I use Startwith(PODNum1) to search first field, and use Contains("-" + PODNum2 + "-") to search second and third strings (replace PODNum1 to PODNum3), and use EndWith("-" + PODNum4) to search 4th string. This way, I don't need to change anything else, it is simple.
Again, thanks a lot for all responses, much appreciated.
If i understand this correct,you have one column which u want to act like 4 different column ? This isn't worth it...For that,you need to Split each rows column data,create a class to handle the splitted data and finally use a `List .Thats a useless workaround.I rather suggest u to use 4 columns instead.
But if you still want to go with your existing applied method,you first need to Split as i mentioned earlier.For that,here's an example :
public void test()
{
SqlDataReader datareader = new SqlDataReader;
while (datareader.read)
{
string part1 = datareader(1).toString.Split("-")(0);///the 1st part of your column data
string part2 = datareader(1).toString.Split("-")(1);///the 2nd part of your column data
}
}
Now,as mentioned in the comments,you can rather a class to handle all the data.For example,let's call it mydata
public class mydata {
public string part1;
public string part2;
public string part3;
public string part4;
}
Now,within the While loop of the SqlDatareader,declare a new instance of this class and pass the values to it.An example :
public void test()
{
SqlDataReader datareader = new SqlDataReader;
while (datareader.read)
{
Mydata alldata = new Mydata;
alldata.Part1 = datareader(1).toString.Split("-")(0);
alldata.Part2 = datareader(1).toString.Split("-")(1);
}
}
Create a list of the class in class-level
public class MyForm
{
List<MyData> storedData = new List<MyData>;
}
Within the while loop of the SqlDatareader,add this at the end :
storedData.Add(allData);
So finally, u have a list of all the splitted data..So write your filtering logic easily :)
As already mentioned in a comment, the error means that accessing data via index (see [0]) is not supported when translating your expression to SQL. Split('-') is also not supported hence you have to resort to the supported functions Substring() and IndexOf(startIndex).
You could do something like the following to first transform the string into 4 number strings ...
.Select(t => new {
t.ProdNum,
FirstNumber = t.ProdNum.Substring(0, t.ProdNum.IndexOf("-")),
Remainder = t.ProdNum.Substring(t.ProdNum.IndexOf("-") + 1)
})
.Select(t => new {
t.ProdNum,
t.FirstNumber,
SecondNumber = t.Remainder.Substring(0, t.Remainder.IndexOf("-")),
Remainder = t.Remainder.Substring(t.Remainder.IndexOf("-") + 1)
})
.Select(t => new {
t.ProdNum,
t.FirstNumber,
t.SecondNumber,
ThirdNumber = t.Remainder.Substring(0, t.Remainder.IndexOf("-")),
FourthNumber = t.Remainder.Substring(t.Remainder.IndexOf("-") + 1)
})
... and then you could simply write something like
if (!string.IsNullOrEmpty(ProdNum3) {
predicate = predicate.And(
t => t.ThirdNumber.Contains(ProdNum3)

using string.split in entity to traverse tree depth

i have the following self-referencing table
public partial class products_category
{
public long id { get; set; }
public string category_name { get; set; }
public string category_description { get; set; }
//self referencing to table id
public Nullable<long> Parent_Id { get; set; }
public string navPath {get; set; }
}
here string navpath contains all the leading parents for a child categroy, say:
"Clothes" = 1 Parent_id=null, navpath=""
"Silk" = 2 Parent_id=1 navpath="1"
"Silk Suit"=3 parent_id=2 navpath="1-2"
"Saree" =4 parent_id=3 navpath="1-2-3"
"Dress Material"=5 parent_id=1 navpath="1" and so on....
now as per this scenario i want to access the flattend tree for frther processing for a certain depth only say to level 2 or until level 4 depth of children associated with navpath.
my idea regarding this issue was to approach using linq to ef this way:
var catTrees = db.products_category.Where(pc => pc.navpath.Split('-').Length < 4).ToList();
i am using the following link to do further traversing and tree generation:
https://bitlush.com/blog/recursive-hierarchical-joins-in-c-sharp-and-linq
and it is doing a great work so far, the only issue is i dont want to pre select whole table for processing. i want to achieve paging and a certain level of depth for first iteration, so i can maintain performance in case of thousand of records. [think of this as a category hierarchy or blog/youtube comments hierarchy].
but using the above ef linq command is giving the following error:
The LINQ expression node type 'ArrayLength' is not supported in LINQ to Entities.
i checked with ef docs and other places in SO to know that string.split doesn't work with EF implicitly. but can we apply it using extension methods or can this tree selection have alternate approach without using string.split and hitting DB only ones?
please advice.
This looks like an issues with building SQL code out of your LINQ mpre specifically SQL which takes a string splits it on dash and counts the elements.
if you dont hate the idea of loading into memory then you can force anything :)
var catTrees = db.products_category.ToList().Where(pc => pc.navpath.Split('-').Length < 4).ToList();
The trick here is to force the execution of the SQL by adding the .ToList() when we want the data from the database. This is called realizing the data.
Even with that realization trick the count is faster
var catTrees = db.products_category.ToList().Where(pc => pc.navpath.Count(a => a == '-') < 3).ToList();
these solutions are essentially the same as
List<Result> filter() {
List<Result> r = new List<Result>();
foreach(var a in db.products_category) {
if(a.navpath.Count(a => a == '-') < 3) {
r.add(a);
}
}
return r;
}
When thinking about it the filter method is somewhat less memory intensive as it reads one and one and never stores everything in memory. (in theory at least, only a few really knows what the .NET compiler does in the shadows)
I would advice you against using the navpath for checking depth.
If you can change your model, you could add an additional numeric Depth field for each category and populate it according its navpath, then you could select them from your code in this way:
var catTrees = db.products_category.Where(pc => pc.Depth < 3).ToList();
There are many ways to populate that new column, but the bottom line is that you will have to do it just once (given that you keep track of it every time you modify the navpath of a category).
One possible way of populating it would be looping through all categories, something like:
var allCategories = db.products_category.ToList();
foreach(var category in allCategories)
{
var depth = category.navpath == "" ? 0 : category.navpath.Split('-').Length + 1;
category.Depth = depth;
}
allCategories.SubmitChanges();

Why don't these ElasticSearch fields appear in "Fields"?

I can't figure this one out. I'm creating a new ElasticSearch index using the ElasticProperty attributes/decorators. Here's how I create the index:
client = ClientFactory(indexName);
if (!client.IndexExists(indexName).Exists)
{
var set = new IndexSettings() { NumberOfShards = 1 };
var an = new CustomAnalyzer() { Tokenizer = "standard" };
an.Filter = new List<string>();
an.Filter.Add("standard");
an.Filter.Add("lowercase");
an.Filter.Add("stop");
an.Filter.Add("asciifolding");
set.Analysis.Analyzers.Add("nospecialchars", an);
client.CreateIndex(c => c
.Index(indexName)
.InitializeUsing(set)
.AddMapping<ItemSearchable>(m => m.MapFromAttributes())
);
}
And all of my fields are being created properly from the attributes specified on the class, except these two. They are C# enumerations, which is maybe part of the problem. I'm trying to save them in the index as numeric fields...
[Required, ElasticProperty(Index = FieldIndexOption.NotAnalyzed, Store = true, NumericType = NumberType.Short, IncludeInAll = false)]
public Enums.PlatformType PlatformType { get; set; }
[ElasticProperty(Index = FieldIndexOption.NotAnalyzed, Store = true, NumericType = NumberType.Short, OmitNorms = true, IncludeInAll = false)]
public Enums.ItemType ItemType { get; set; }
When I set up the index and check via Kibana, I don't see PlatformType or ItemType at all in the list of fields in the empty index.
When I insert a record, I can see the values in the source (JSON) as numbers (as expected), but the "Fields" are not there.
So I'm thinking it's either because they're C# enum type, or because I'm trying to store it as a number. But I'm stumped on why Elasticsearch is skipping these fields. Many thanks for any ideas you may have.
UPDATE 1 ... The search still works (even without those fields being shown in the Fields section). I'm thinking it might just be a Kibana problem. In the Table view, it shows my two fields like this...
and hovering over those triangle exclamation marks says "No cache mapping for this field. Refresh your mapping from the Settings > Indices page". But of course I can't find such a page within Kibana.
So I might be fine behind the scenes and this is a non-issue. Does anyone else have any insight on what might fix this, make it clearer, or whether this is known behaviour and I should just move on? Thanks.

Silverlight C#: How to filter a combobox based on another combobox?

How to filter a combobox based on another combobox? ... again :)
I'm writing an web app to learn. I'm using Visual Studio 2012, Silverlight 5, C#, and SQl Server for the data source.
I have one table loading into a datagrid and comboboxes to filter the datagrid. Up to this point everything is working just right.
The comboboxes are "FilterState" and "FilterWaterWay". Note they are not in the datagrid.
I want to select a state and re-populate the FilterWaterWay with only those waterways in the state.
I've seen a lot of ways to do this but none of them seem to match my setup. I could be wrong and just not know it.
From a learning standpoint, I would like to know how to implement this in all 3 of the following query data examples but I'll settle for just one. The last one is my favorite.
Thanks for any and all help.
I would not mind using the following to load comboboxes, filtered or not, but I can't firgure out how to
Restirct the GetQuery to only one field
Make that field distinct
This loads all data from the GetQuery to the datagrid.
LoadOperation<MASTER_DOCKS> loadOp = this._DocksContext.Load(this._DocksContext.GetMASTER_DOCKSQuery());
DocksGrid.ItemsSource = loadOp.Entities;
This loads all data from the GetQuery to the datagrid after it's been filtered
EntityQuery<MASTER_DOCKS> query = _DocksContext.GetMASTER_DOCKSQuery();
query = query.Where(s => s.WTWY_NAME == WaterwaytoFilterBy && s.STATE == StateToFilterBy);
LoadOperation<MASTER_DOCKS> loadOp = this._DocksContext.Load(query);
DocksGrid.ItemsSource = loadOp.Entities;
This is how I am currently loading the comboboxes. This works fine for the load but I don't see how to filter.
The DomainService.cs does not know my other combobox (FilterState) that I want to use as the filter for this combobox (FilterWaterway).
If I could query the ObservableCollection in the xaml I might be able to get it to work but it seems kind of chunky.
Adapted from http://www.jonathanwax.com/2010/10/wcf-ria-services-datagrid-filters-no-domaindatasource-2/
XAML =
private ObservableCollection<string> waterWayFilterList;
public ObservableCollection<string> WaterWayFilterList
{
get { return waterWayFilterList; }
set { waterWayFilterList = value; }
}
private void DoPopulateFilter()
{
//Call Invoke Method to get a list of distinct WaterWays
InvokeOperation<IEnumerable<string>> invokeOp = _DocksContext.FillWaterWayList();
invokeOp.Completed += (s, e) =>
{
if (invokeOp.HasError)
{
MessageBox.Show("Failed to Load Category Filter");
}
else
{
//Populate Filter DataSource
WaterWayFilterList = new ObservableCollection<string>(invokeOp.Value);
//Add a Default "[Select]" value
WaterWayFilterList.Insert(0, "[Select WaterWay]");
FilterWaterWay.ItemsSource = WaterWayFilterList;
FilterWaterWay.SelectedItem = "[Select WaterWay]";
}
};
}
DomainService.cs =
[Invoke]
public List<string> FillWaterWayList()
{
return (from r in ObjectContext.MASTER_DOCKS
select r.WTWY_NAME).Distinct().ToList();
}
Here's the closest I've gotten so far and it seems straight forward.
It returns no errors but the displayed result reads System.Collections.Generic.List'1[System.Char]
The record count in the dropdown is correct which leads me to think it's on the right track.
Only what is displayed is wrong. A casting problem perhaps?
I would still have to get the result from the FilterState Combo box in where "TX" is.
var filter = from r in _DocksContext.MASTER_DOCKS
where r.STATE.Equals("TX")
select r.WTWY_NAME.Distinct().ToList();
MyComboBox.ItemsSource = filter;
Without parentheses, you're doing the .Distinct().ToList() on the string (which implements IEnumerable<char>, which is why those operations work), which results in a List<char> (which isn't what you're looking for). You need to add parentheses so you get the distinct waterways:
var filter = (from r in _DocksContext.MASTER_DOCKS
where r.STATE.Equals("TX")
select r.WTWY_NAME).Distinct().ToList();
Note that if two waterways might have the same name, but actually be distinct, you'll need to instead select distinct r, and then differentiate them in the dropdown somehow, e.g.
var filter = (from r in _DocksContext.MASTER_DOCKS
where r.STATE.Equals("TX")
select r).Distinct().ToList();
// generated classes are partial, so you can extend them in a separate file
public partial class MASTER_DOCKS
{
// the dropdown uses the ToString method to show the object
public override string ToString()
{
return string.Format("{0} ({1})", WTWY_NAME, ID);
}
}

What is the correct way of using Entity Framework?

I have a DB like this that I generated from EF:
Now I'd like to add a "fielduserinput" entity so I write the following code:
public bool AddValueToField(string field, string value, string userId)
{
//adds a value to the db
var context = new DBonlyFieldsContainer();
var fieldSet = (from fields in context.fieldSet
where fields.fieldName.Equals(field)
select fields).SingleOrDefault();
var userSet = (from users in context.users
where users.id.Equals(userId)
select users).SingleOrDefault();
var inputField = new fielduserinput { userInput = value, field = fieldSet, user = userSet };
return false;
}
Obviously it's not finished but I think it conveys what I'm doing.
Is this really the right way of doing this? My goal is to add a row to fielduserinput that contains the value and references to user and field. It seems a bit tedious to do it this way. I'm imagining something like:
public bool AddValueToField(string userId, string value, string fieldId)
{
var context = new db();
var newField = { field.fieldId = idField, userInput = value, user.id = userId }
//Add and save changes
}
For older versions of EF, I think you're doing more or less what needs to be done. It's one of the many reasons I didn't feel EF was ready until recently. I'm going to lay out the scenario we have to give you another option.
We use the code first approach in EF 4 CTP. If this change is important enough, read on, wait for other answers (because Flying Speghetti Monster knows I could be wrong) and then decide if you want to upgrade. Keep in mind it's a CTP not an RC, so considerable changes could be coming. But if you're starting to write a new application, I highly recommend reading some about it before getting too far.
With the code first approach, it is possible to create models that contain properties for a reference to another model and a property for the id of the other model (User & UserId). When configured correctly setting a value for either the reference or the id will set the id correctly in the database.
Take the following class ...
public class FieldUserInput{
public int UserId {get;set;}
public int FieldId {get;set;}
public virtual User User {get;set;}
public virtual Field Field {get;set;}
}
... and configuration
public class FieldUserInputConfiguration{
public FieldUserInputConfiguration(){
MapSingleType(fli => new {
userid = fli.UserId,
fieldid = fli.FieldId
};
HasRequired(fli => fli.User).HasConstraint((fli, u)=>fli.UserId == u.Id);
HasRequired(fli => fli.Field).HasConstraint((fli, f)=>fli.FieldId == f.Id);
}
}
You can write the code...
public void CreateField(User user, int fieldId){
var context = new MyContext();
var fieldUserInput = new FieldUserInput{ User = user, FieldId = fieldId };
context.FieldUserInputs.Add(fieldUserInput);
context.SaveChanges();
}
... or vice versa with the properties and everything will work out fine in the database. Here's a great post on full configuration of EF.
Another point to remember is that this level of configuration is not necessary. Code first is possible to use without any configuration at all if you stick to the standards specified in the first set of posts referenced. It doesn't create the prettiest names in the database, but it works.
Not a great answer, but figured I'd share.

Categories