The Realm documentation gives an example of backlinks using a person object and a dog object. If I extend this to include cats also, so a person can have several dogs or cats to walk, and each dog or cat can be walked by several different people.
public class Dog : RealmObject
{
public string Name { get; set; }
[Backlink(nameof(Person.Dogs))]
public IQueryable<Person> Walkers { get; }
}
public class Cat : RealmObject
{
public string Name { get; set; }
[Backlink(nameof(Person.Cats))]
public IQueryable<Person> Walkers { get; }
}
public class Person : RealmObject
{
//... other properties (name, age, address, etc.)
public IList<Dog> Dogs { get; }
public IList<Cat> Cats { get; }
}
Using the backlinks lets me get a list of people who walk the dog Fido...
var fidoWalkers = realm.All<Dog>().Where( d => d.Name == "Fido").FirstOrDefault().Walkers;
I can now further expand this query to find walkers of Fido who live in High Street or who are under 30 years old or whatever... great so far.
Now I want to get a list of people who walk the dog Fido and the cat Moggie. Using the backlinks in two separate statements I could get two result sets, one for Fido walkers and one for Moggie walkers, but I don't know how to combine them. Neither can I work out a query that would let me do this 'the long way round' without using the backlinks, because whenever I try to use
...Where( P => p.Dogs.Contains(Fido))...
I get 'System.NotSupportedException: The method 'Contains' is not supported'
Is there any way to get a list of people filtered by both the Dogs and the Cats lists?
While there are many things the .Net version of Realm does well, there are limitations in the Linq support and thus you are forced to materialize the lazy loaded IRealmCollection to a hard List (or array) via LINQ to Objects in order to perform projections, joins, IEqualityComparer comparisons, etc... this includes Backlinks and RealmObject-based IList properties.
A must read Realm-dotnet document: LINQ support in Realm Xamarin
You could add couple of properties that perform a Linq projection to a list of strings (pet names, of course assuming they are unique keys):
public class Person : RealmObject
{
//... other properties (name, age, address, etc.)
public IList<Dog> Dogs { get; }
public IList<Cat> Cats { get; }
public List<string> DogList => Dogs.ToList().Select(_ => _.Name).ToList();
public List<string> CatList => Cats.ToList().Select(_ => _.Name).ToList();
}
And then take your person query to a list and use Contains on the materialized string lists vs. the RealmCollection properties.
var walkers = realm.All<Person>().ToList().Where(_ => _.CatList.Contains("Garfield") && _.DogList.Contains("Fido"));
foreach (var walker in walkers)
{
Log.Debug(TAG, $"{walker}");
}
You could also create two queries, one filtered on the dog's name backlink and cats for the other, materialized all the Dog and Cat collections on each Person in both queries, perform a Distinct via a custom IEqualityComparer.
Once you have copied the objects out of Realm into independent lists/arrays, just about any Linq solution will work...
The downside of this is all the filtering is being performed in memory thus you take a memory and CPU hit as you are not able to use Realm's native query features...
Personally, I would re-model the RealmObject's to provide people that walk animals and thus animals are generic and they in turn provide links to particular Dog and Cat RealmObjects that provide specialization. But that is totally without knowing your end goal...
Related
I have noticed that when I do a LINQ - group by, it only seems to be working if I dont group on any of my custom classes.
I have a Product class (shown below) and I would like to group on Product.Id ,Product.Variant (object) and on Product.Options (ICollection) (because my source list contains multiple times the same Product but with different Variants and / or Options)
Product:
public class Product
{
public int Id { get; set; }
public Variant Variant { get; set; }
public ICollection<Option> Options{ get; set; }
public string Name { get; set; }
public int Amount { get; set; }
}
The code below will do a grouping, but only on my Product.ID, when I try to also group on Variant / Options, I get no grouping (well, not the grouping I am intending to make) as it will return just as much items as my source list.
IEnumerable<productAndSum> productsAndSums = unmappedProducts
.GroupBy(prod => new { Id = prod.Id})
.Select(group => new productAndSum()
{
Key = group.Key,
Sum = group.Sum(x => x.Amount)
});
If I am on the right track and the issue is related to the Objects, then it might be usefull to add that also Option and Variant have multiple objects and collections themselves, or is this too deep?
Extra information: I first started to just group by my Product class (not Id, Variant & Options separately), but this was unsuccessful. so I started eliminating properties and this is how I found out this issue. I think that solving this issue will result in killing two birds with one stone.
Warm regards
I tried to get all persons who have dogs older than 10 using the following line of code :
var persons = realm.All<Person>()
.Where(person => person.Dogs.Count(dog=>dog.Age > 10) > 0);
But I got this error:
System.NotSupportedException:
The lhs of the binary operator 'GreaterThan' should be a member expression.
Unable to process `person.Dogs.Count(dog => (dog.Age > 10))`
Does it mean that we are not able to filter on relations in Realm ?
Here are the models that I copied from Realm documentation:
public class Dog : RealmObject
{
public string Name { get; set; }
public int Age { get; set; }
public Person Owner { get; set; }
}
public class Person : RealmObject
{
public string Name { get; set; }
public IList<Dog> Dogs { get; }
}
Yeah sorry, this type of query is not yet supported. We are working on improving LINQ coverage, but I'm afraid you won't be able to perform this particular query in the near future.
Two workarounds that may or may not be applicable in your situation:
1) Add a boolean HasOldDogs property on your Person class. Note that in order to perform queries on it, it would have to be a persisted property so you would have to update it explicitly whenever you modify the Dogs property or any of the contained Dog instances.
2) Perform the query in memory. var persons = realm.All<Person>().ToList().Where( ... ) will work. However, this will perform the query with LINQ to Objects in memory which works best if you don't have a massive number of Person instances.
If you can describe in more detail what your situation requires, we might be able to think up alternatives.
may be you have to break your query to two queries instead of one.
Seems there are some issues with Realm that might require someone from Realm team to explain :)
We have a lot of Dto classes in our project and on various occasions SELECT them using Expressions from the entity framework context. This has the benefit, that EF can parse our request, and build a nice SQL statement out of it.
Unfortunatly, this has led to very big Expressions, because we have no way of combining them.
So if you have a class DtoA with 3 properties, and one of them is of class DtoB with 5 properties, and again one of those is of class DtoC with 10 properties, you would have to write one big selector.
public static Expression<Func<ClassA, DtoA>> ToDto =
from => new DtoA
{
Id = from.Id,
Name = from.Name,
Size = from.Size,
MyB = new DtoB
{
Id = from.MyB.Id,
...
MyCList = from.MyCList.Select(myC => new DtoC
{
Id = myC.Id,
...
}
}
};
Also, they cannot be reused. When you have DtoD, which also has a propertiy of class DtoB, you would have to paste in the desired code of DtoB and DtoC again.
public static Expression<Func<ClassD, DtoD>> ToDto =
from => new DtoD
{
Id = from.Id,
Length = from.Length,
MyB = new DtoB
{
Id = from.MyB.Id,
...
MyCList = from.MyCList.Select(myC => new DtoC
{
Id = myC.Id,
...
}
}
};
So this will escalate pretty fast. Please note that the mentioned code is just an example, but you get the idea.
I would like to define an expression for each class and then combine them as required, as well as EF still be able to parse it and generate the SQL statement so to not lose the performance improvement.
How can i achieve this?
Have you thought about using Automapper ? You can define your Dtos and create a mapping between the original entity and the Dto and/or vice versa, and using the projection, you don't need any select statements as Automapper will do it for you automatically and it will project only the dto's properties into SQL query.
for example, if you have a Person table with the following structure:
public class Person
{
public int Id { get; set; }
public string Title { get; set; }
public string FamilyName { get; set; }
public string GivenName { get; set; }
public string Initial { get; set; }
public string PreferredName { get; set; }
public string FormerTitle { get; set; }
public string FormerFamilyName { get; set; }
public string FormerGivenName { get; set; }
}
and your dto was like this :
public class PersonDto
{
public int Id { get; set; }
public string Title { get; set; }
public string FamilyName { get; set; }
public string GivenName { get; set; }
}
You can create a mapping between Person and PersonDto like this
Mapper.CreateMap<Person, PersonDto>()
and when you query the database using Entity Framework (for example), you can use something like this to get PersonDto columns only:
ctx.People.Where(p=> p.FamilyName.Contains("John"))
.Project()
.To<PersonDto>()
.ToList();
which will return a list of PersonDtos that has a family name contains "John", and if you run a sql profiler for example you will see that only the PersonDto columns were selected.
Automapper also supports hierachy, if your Person for example has an Address linked to it that you want to return AddressDto for it.
I think it worth to have a look and check it, it cleans a lot of the mess that manual mapping requires.
I thought about it a little, and I didn't come up with any "awesome" solution.
Essentially you have two general choices here,
Use placeholder and rewrite expression tree entirely.
Something like this,
public static Expression<Func<ClassA, DtoA>> DtoExpression{
get{
Expression<Func<ClassA, DtoA>> dtoExpression = classA => new DtoA(){
BDto = Magic.Swap(ClassB.DtoExpression),
};
// todo; here you have access to dtoExpression,
// you need to use expression transformers
// in order to find & replace the Magic.Swap(..) call with the
// actual Expression code(NewExpression),
// Rewriting the expression tree is no easy task,
// but EF will be able to understand it this way.
// the code will be quite tricky, but can be solved
// within ~50-100 lines of code, I expect.
// For that, see ExpressionVisitor.
// As ExpressionVisitor detects the usage of Magic.Swap,
// it has to check the actual expression(ClassB.DtoExpression),
// and rebuild it as MemberInitExpression & NewExpression,
// and the bindings have to be mapped to correct places.
return Magic.Rebuild(dtoExpression);
}
The other way is to start using only Expression class(ditching the LINQ). This way you can write the queries from zero, and reusability will be nice, however, things get harder & you lose type safety. Microsoft has nice reference about dynamic expressions. If you structure everything that way, you can reuse a lot of the functionality. Eg, you define NewExpression and then you can later reuse it, if needed.
The third way is to basically use lambda syntax: .Where, .Select etc.. This gives you definitely better "reusability" rate. It doesn't solve your problem 100%, but it can help you to compose queries a bit better. For example: from.MyCList.Select(dtoCSelector)
I'm having a performance issue with lookups using the navigation properties of an EF model.
My model is something like this (conceptually):
public class Company
{
public int ID { get; set; }
public string CompanyName { get; set; }
public EntityCollection<Employee> Employees { get; set; }
}
public class Employee
{
public int CompanyID { get; set; }
public string EmployeeName { get; set; }
public EntityReference<Company> CompanyReference { get; set; }
}
Now let's say I want to get a list of all Companies that have (known) Employees.
Additionally, assume that I've already cached lists of the both the Companies and the Employees through previous calls:
var dbContext = new EmploymentContext();
var allCompanies = dbContext.Companies.ToList();
var allEmployees = dbContext.Employees.ToList();
bool activeCompanies =
allCompanies.Where(company => company.Employees.Any()).ToList();
This (in my environment) generates a new SQL statement for each .Any() call, following the Employees navigation property.
I already have all the records I need in my cached lists, but they're not 'connected' to each other on the client side.
I realize I can add .Include() calls to my initial cache-fill statement. I want to avoid doing this because in my actual environment I have a large number of relations and a large number of lists I'm populating up front. I'm caching largely to keep Linq from generating overly-complicated nested SQL statements that tend to bog down my database server.
I also realize I can modify my query so as to do an in-memory join:
bool activeCompanies = allCompanies.Where
(
company => allEmployees.Any(employee => employee.CompanyID == company.ID)
);
I'm trying to avoid doing such a rewrite, because the actual business logic gets rather involved. Using Linq statements has significantly improved the readability of this logic, and I'd prefer not to lose that if at all possible.
So my question is this: can I connect them together manually somehow, in the way that the Entity Framework would connect them?
I'd like to continue to use the .Any() operator, but I want it to examine only the objects I have in memory in my dbContext - without going back to the database repeatedly.
I'm hoping this will be a rather simple question for anyone who's good at Linq. I'm struggling to come up with the right Linq expression for the following. I'm able to hack something to get the results, but I'm sure there's a proper and simple Linq way to do it, I'm just not good enough at Linq yet...
I have a database accessed through Entity Framework. It has a number of Tasks. Each Task has a collection of TimeSegments. The TimeSegments have Date and Employee properties.
What I want is to be able to get the tasks for a certain employee and a certain month and the timesegments for each task for that same month and employee.
Again, the tasks do not in themselves have month nor date information, but they do by the TimeSegments associated with each task.
Very simplified it looks sort of like this:
public class Model //Simplified representation of the Entity Framework model
{
public List<Task> Tasks { get; set; }
}
public class Task
{
public int Id { get; set; }
public List<TimeSegment> TimeSegments { get; set; }
public Customer Customer { get; set; }
}
public class TimeSegment
{
public int Id { get; set; }
public string Date { get; set; }
public Employee Employee { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
}
So how do I do this as simply as possible with Linq? I.e. tasks and associated timesegments for a certain month and employee. I would also like to be able to get it by Customer BTW...
This is the simplest thing I could come up with:
var tasksWithSegments =
from segment in model.TimeSegments
where segment.Date.Month == month
where segment.Employee.Id == employeeId
group segment by segment.Task into result
select new
{
Task = result.Key,
TimeSegments = result.ToArray()
};
Please note that you might have to add some properties to your model, such as Model.TimeSegment and TimeSegment.Task.
The trick with LINQ queries often is to start at the right collection. In this case the ideal starting point is TimeSegments.
ps. I'm not sure whether Date.Month == month will actually work with EF, but I think it will (with EF 4.0 that is).
Update:
Could you show how to extend this
query and get the tasks for a
particular Customer as well?
I'm not sure what you mean, but you can for instance filter the previous queryable like this:
var tasksWithSegmentsForCustomers =
from taskWithSegments in tasksWithSegments
where taskWithSegments.Task.Customer.Id == customerId
select taskWithSegments;
Can I get the return type to be a list
of Tasks with a list of TimeSegments
if I have this in a method?
Again, not sure what you exactly want, but if you want two separate lists that have no relation, you can do this:
List<Task> tasks = (
from taskWithSegments in tasksWithSegments
select taskWithSegments.Task).ToList();
List<TimeSegments> segments = (
from taskWithSegments in tasksWithSegments
from segment in taskWithSegments.Segments
select segment).ToList();
Of course, if this is what you need, than it might be easier to rewrite the original query to something like this:
List<TimeSegment> segments = (
from segment in model.TimeSegments
where segment.Date.Month == month
where segment.Employee.Id == employeeId
select segment).ToList();
List<Task> allTasks =
segments.Select(s => s.Task).Distinct().ToList();
Once you got the hang of writing LINQ queries, there is no way you want to go back to writing SQL statements or old-fashion foreach statements.
Think LINQ!!!
What I want is to be able to get the
tasks for a certain employee and a
certain month and the timesegments for
each task for that same month and
employee.
This will select tasks from an instance of Model where the task has at least one time segment that in the requested month for the requested employee (untested):
Model model = new Model();
tasks = model.Tasks.Where(t => t.TimeSegments.Any(ts => ts.Employee.Id = requestedId && Convert.ToDate(ts.Date).Month == requestedMonth));