Realm Xamarin LINQ Object - linq

What is the correct way to query Realm with LINQ where the query includes fields from other Realm objects? For example:
public class Department : RealmObject
{
[Primary Key]
public string UniqueId { get; set; }
}
public class Employee : RealmObject
{
[Primary Key]
public string Name { get; set; }
// Parent
public Department Department { get; set; }
}
Then I would expect to be able to do something like:
var employee = realm.All<Employee>().SingleOrDefault( e => e.Department.UniqueId == fooId && e.Name == fooName );
But this always returns no matches. Where() also returns no matches. However, eliminating the e.Department and searching only on employee name works fine but obviously does not scope to Department as intended.
This is with the latest Realm Xamarin 0.80.
What am I doing wrong?

Querying by nested RealmObjects attributes is not currently supported:
Just to clarify here, we don't yet support queries on related objects like this. We will in the future, but there is no timeline at the moment.
The following is also not currently supported:
var deptFilter = theRealm.ObjectForPrimaryKey<Department>("HR");
var employeesByDept = theRealm.All<Employee>().Where((Employee emp) => emp.Department == deptFilter & emp.Name == "StackOverflow");
The left-hand side of the And operator must be a direct access to a persisted property in Realm.
Unable to process '(emp.Department == value(Realm080.App+c__AnonStorey1).deptFilter)'.
You can do direct RealmObject equalities, just not in the same Linq expression, so break it down further into a sub-query.
Example of how I currently do it:
var deptFilter = theRealm.ObjectForPrimaryKey<Department>("HR");
var employeesByDept = theRealm.All<Employee>().Where((Employee emp) => emp.Department == deptFilter);
var employees = employeesByDept.Where((Employee emp) => emp.Name == "StackOverflow");
foreach (var emp in employees)
{
D.WriteLine(emp.Name);
}
Note: https://github.com/realm/realm-dotnet/issues/723

Related

Can I declare a function inside a LINQ select clause?

I'm studying for MS Exam 70-483 (C#) and this idea occurred to me.
Is it possible to do something like this:
class Person
{
public string Name { get; set; }
public int CityId { get; set; }
}
class City
{
public string Name { get; set; }
public int Id { get; set; }
}
/* assume code to populate List people and List cities */
var personsByCity = from p in people join c in cities
on p.CityId equals c.Id
select new { PersonName = p.Name, CityName = c.Name,
ToString = Func<string>( () => { return PersonName + "/" +
CityName; })};
The compiler error that occurs is "[CS0119] Expression denotes a type' where avariable' or `method group' was expected.
Resharper is a bit clearer: Delegate name is not valid at this point (i.e. where Func occurs).
So, is there a way to accomplish adding a function to an anonymous type in a LINQ query?
Yes, you can declare a function, but you can't define it to return a property of the anonymous type that is currently being declared-- if you were to use this, for example, the lambda would capture the value of this in the declaring context (the class that contains the code that is executing the LINQ). You can however capture the values that are being used to initialize the anonymous class (as I do in the example below).
Note that this is not the same as giving the anonymous class a method. Anonymous classes can only have read-only, immutable properties. In this case, your anonymous class will still have the original ToString() method inherited from object. This may make it tricky to distinguish the function held in the ToString property from the ToString() method, so maybe you should use a different name.
Also, you forgot the new keyword before Func.
var personsByCity = from p in people join c in cities on p.CityId equals c.Id
select new
{
PersonName = p.Name,
CityName = c.Name,
ToString = new Func<string>( () => {
return c.Name + "/" + p.Name;
})
};

linq any clause on multiple values

I am trying to create a linq statement to filter otu results using an any clause. My issue is that I dont have a single value to compare against.
In the example below I have a PropertyTaxBill entity that is the parent. Each one has a collection of TaxPropertyAssessmentDetails attached to it.
In this query people can specify that they only want to deal with bills pertaining to a specific class strata so I check to see if any values exist in the classStrata variable. If so then the user selected specific ones. I was trying to do an any clause on the classStrata but instead of giving it a single value to match on I was trying to select all the values in the TaxPropertyAssessmentDetails collection attached to the PropertyTaxBill. Is this possible?
using (var dataContext = contextProvider.GetContext())
{
var query = dataContext.PropertyTaxBills.Where(x => x.Id > 1);
var classStrata = new int[0];
if (classStrata != null && classStrata.Any())
{
query = query.Where(x => classStrata.Any(y => y == x.TaxPropertyAssessmentDetails.SelectMany(z => z.PropertyTaxClassStrataId)));
}
}
You need to use contains so that ef is able to translate to a ef query.
Not sure that i understood your model correctly. Here's a example with a model. Please tell me if i understood incorrect so i can fix it.
public void TestMethod1()
{
IEnumerable<TaxBill> TaxBills = new List<TaxBill>();
var query = TaxBills.Where(x => x.Id > 1);
var classStrata = new int[0];
if (classStrata != null && classStrata.Any())
{
query = query.Where(x => x.AssessmentDetails.Any(ad => ad.TaxClassStrataId.Any(cs => classStrata.Contains(cs))));
}
}
And the entities
public class TaxBill
{
public int Id { get; set; }
public ICollection<AsessmentDetails> AssessmentDetails { get; set; }
}
public class AsessmentDetails
{
public ICollection<int> TaxClassStrataId { get; set; }
}

Join LINQ queries from multiple contexts

I am trying to create a ViewModel for data that I want to display in my view. The issue is, the data being displayed is spread across 2 databases and multiple tables within each. I've read that you cannot join Linq queries across multiple contexts, which makes sense, and I've also read that we can't use Code-First in EF5 to use Stored Procedures....which led me to using 3 different Linq queries and attempt to combine them into 1 ViewModel...I'm just not sure how to get there.
Here's my Linq queries:
var csdContext = new CSDContext(CustomerCode);
var masterContext = new MasterContext();
//Only returns 1 row - which is what we want.
List<Site> sites = (from s in csdContext.Sites
join sa in csdContext.SiteAddresses
on s.SiteID equals sa.SiteID
join a in csdContext.Addresses
on sa.AddressID equals a.AddressID
join spv in csdContext.SiteProductVersions
on s.SiteID equals spv.ProductVersionID
where s.SiteID == id
select s).ToList();
//List
List<States> states = (from s in masterContext.StatesTable
select s).ToList();
My ViewModel looks like this:
public class SiteDetailsViewModel
{
public string Address { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string StateCode { get; set; }
public string ZipCode { get; set; }
public string OfficePhone { get; set; }
public string MobilePhone { get; set; }
public string AlternativePhone { get; set; }
public int ProductVersionID { get; set; }
}
Basically, I need the following data from these tables:
csdContext - Address
Address
Address2
City
ZipCode
csdContext - Sites
OfficePhone
MobilePhone
AlternativePhone
csdContext - SiteProductVersions
ProductVersionID
masterContext - States
StateCode
Here's how the tables are joined in SQL:
SELECT csd_a.Address, csd_a.Address2, csd_a.City, mstr_st.StateCode, csd_a.ZipCode, csd_s.OfficePhone, csd_s.MobilePhone,
csd_s.AlternativePhone, csd_spv.ProductVersionID
FROM CSD.dbo.Sites AS csd_s
INNER JOIN CSD.dbo.SiteAddress AS csd_sa ON csd_sa.SiteID = csd_s.SiteID
INNER JOIN CSD.dbo.Address AS csd_a ON csd_a.AddressID = csd_sa.AddressID
INNER JOIN CSD.dbo.SiteProductVersions AS csd_spv ON csd_s.SiteID = csd_spv.SiteID
INNER JOIN MasterDB.dbo.States AS mstr_st ON mstr_st.StateID = csd_a.StateID
I can't figure out how to merge these 3 results to create the ViewModel data for SiteDetailsViewModel. Can anyone help?
If you materialize your queries as described in OP you can use this:
var query = sites.Join(
states,
si => si.StateID,
st => st.StateID,
(si, st) => new SiteDetailsViewModel
{
Address = si.Address,
Address2 = si.Address2,
City = si.City,
StateCode = st.StateCode,
ZipCode = si.ZipCode,
OfficePhone = si.OfficePhone,
MobilePhone = si.MobilePhone,
AlternativePhone = si.AlternativePhone,
ProductVersionID = siProductVersionID
});
One possible alternate solution is to create views in DB #2 of the tables in DB #1. Then you can model/map those views in EF for DB #2. You would of course still need a context for DB #1 if you need to update any of the tables. But the benefit of this solution is that you can do all of your read access on a single context and get joins at the server instead of in memory.
So why don't you write it like this:
from ...
...
where s.SiteID == id
select new SiteDetailsViewModel()
{
Address = sa.Address,
Address2 = as.Address2,
..
}

LINQ query returning a List<> as a class member

Given the follow data class,
public class EmployeeMenu
{
public int ID { get; set; }
public string HeaderName { get; set; }
public List<string> ItemNames { get; set; }
}
how can I get a sub-query into the ItemNames field?
My current query of
IQueryable<EmployeeMenu> retValue =
from mh in menuHeaders
select new EmployeeMenu
{
ID = mh.ID,
HeaderName = mh.HeaderName,
ItemNames = (from mhi in mh.MenuItems
select mhi.MenuItemName).ToList<string>()
};
doesn't seem to be doing the trick...
The data structure is
MenuHeaders MenuItems
----------- ---------
ID ID
HeaderName <-(FK)--MenuHeaderID
MenuItemName
I ended up just changing from a List to IEnumerable. This fixed it.
Wouldnt you want to just put a where in your sub-select to filter that down to all the menu items with the MenuHeaderID equals mh.HeaderName. You can just .Equals() with the StringComparison type if you want as well.
Here is an example...
IQueryable<EmployeeMenu> retValue =
from mh in menuHeaders
select new EmployeeMenu
{
ID = mh.ID,
HeaderName = mh.HeaderName,
ItemNames = (from mhi in mh.MenuItems
select mhi.MenuItemName where mhi.MenuHeaderID = mh.HeaderName).ToList<string>()
};
My guess is that your not initiliazing the list within your class. I basing this off the experience I was having with Nhibernate.
public class EmployeeMenu
{
public int ID { get; set; }
public string HeaderName { get; set; }
public List<string> ItemNames { get; set; }
public EmployeeMenu()
{
ItemNames=new List<string>();
}
}
Hope this helps.
Okay. Try replacing
(from mhi in mh.MenuItems
select mhi.MenuItemName).ToList<string>()
by
mh.MenuItems
.AsEnumerable()
.Select(mhi => mhi.MenuItemName)
.ToList()
I question if you want a where clause in there somewhere, but this should get you past the runtime exception.
Any time you see an error message of the form "LINQ to Entities does recognize the method ... and this method can not be translated into a store expression" LINQ to Entities is telling you that it can't figure out how to translate part of the expression tree into a SQL statement. This means you need to pull things client side so that LINQ to Entities doesn't try to translate something that it can't translate.

Linq2Sql relationships and WCF serialization problem

here is my scenario
i got
Table1
id
name
Table2
id
family
fid
with one to many relationship set between Table1. id and Table2.fid
now here is my WCF service Code
[OperationContract]
public List<Table1> GetCustomers(string numberToFetch)
{
using (DataClassesDataContext context = new DataClassesDataContext())
{
return context.Table1s.Take(int.Parse(numberToFetch)).ToList( );
}
}
and my ASPX page Code
<body xmlns:sys="javascript:Sys"
xmlns:dataview="javascript:Sys.UI.DataView">
<div id="CustomerView"
class="sys-template"
sys:attach="dataview"
dataview:autofetch="true"
dataview:dataprovider="Service2.svc"
dataview:fetchParameters="{{ {numberToFetch: 2} }}"
dataview:fetchoperation="GetCustomers">
<ul>
<li>{{family}}</li>
</ul>
</div>
though i set serialization mode to Unidirectional in Linq2Sql designer i am not able to get the family value and all what i get is this in firebug
{"d":[{"__type":"Table1:#","id":1,"name":"asd"},{"__type":"Table1:#","id":2,"name":"wewe"}]}
any help would be totally appreciated
Well, the point is: your method GetCustomers only ever selects from Table1 - I don't see any reference at all to Table2, where your Family column is located......
You need to check into Linq-to-SQL JOIN's and how to fetch data from a joined table into your result set.
Something along the lines of:
[DataContract]
class JoinedResult
{
[DataMember]
public int Table1ID { get; set; }
[DataMember]
public string Table1Name { get; set; }
[DataMember]
public string Table2Family { get; set; }
}
[OperationContract]
public List<JoinedResult> GetCustomers(int numberToFetch)
{
using (DataClassesDataContext context = new DataClassesDataContext())
{
var q = from t1 in context.Table1
join t2 in context.Table2 on t1.id = t2.fid
select new JoinedResult
{ Table1ID = t1.ID,
Table1Name = t1.Name,
Table2Family = t2.Family };
return q.Take(numberToFetch).ToList();
}
}
and btw: you should really make numberToFetch an INT parameter! Let the caller make the conversion......
UPDATE: if you don't want to explicitly include the second table to be queried, you could instead add a DataLoadOption to your context:
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<Table1>(t => t.Table2);
context.LoadOptions = dlo;
In that case, you tell LINQ to always include all elements from Table2 when it loads anything from Table1 - that should work, too.

Resources