Using .Include with criteria - entity-framework-core

I have a Verse class which has a list of VerseTranslation.
When I fetch the verse I only want some of the verse translations. Is there a way to do something like this?
var desiredTranslationCodes = new List<string> { "Code1", "Code2" };
var result = Context.Verses.Where(v => v.Chapter == 1 && v.VerseNumber == 3)
.Include(v => v.Translations, t => desiredTranslationCodes.Contains(t.TranslatorCode))
I am only going to convert these to a view model. It seems a big waste to load all VerseTranslations when I might only want 2 of the 10 translations.

I think you can use projections to anonymous or new type. Try the following. Hope to help, my friend :))
var result = Context.Verses.Select(c => new
{
Data = c,
Translations = c.Translations.OrderBy(p => p.Name).Take(2)
});

Related

Is it possible to combine multiple IQueryable into a single one without using Union?

Good evening. Is it possible to combine multiple IQueryable into a single one without using Union in EF Core 2? The main problem is that it doesn't allow to use methods like .Union(), .Except() etc.
It's important that IQueryable should be executed as a single query.
This is what I want to get: toCreateSubquery.Union(toDeleteSubquery)
The queries I want to combine are listed below.
var toCreateSubquery =
validLinks
.Where(
rl => !existingLinks.Any(
el => el.Contract == rl.Contract && el.Estate == rl.Estate)) // Except() doesn't work'
.Select(x => new {x.Contract, x.Estate, Type = ActionType.Create});
var toDeleteSubquery =
existingLinks
.Where(el => !validLinks.Any(rl => el.Contract == rl.Contract && el.Estate == rl.Estate))
.Select(x => new {x.Contract, x.Estate, Type = ActionType.Delete});
This is the visualization of the problem I'm solving.
I want to get the union of these sets without intersection and be able to distinguish belonging to one of these sets.
Just in case, I attach the code of getting these sets:
var validLinks = from ac in _context.AreaContracts
from e in _context.Estate
where (from al in e.AreaLinks select al.Area.Id).Contains(ac.Area.Id) ||
e.Geometry.Intersects(
ac.Area.Geometry)
select new { Contract = ac.Contract.Id, Estate = e.Id };
var existingLinks = from sd in _context.SquareDistributions
select new { Contract = sd.Contract.Id, Estate = sd.Estate.Id };
Thank you for your attention.

EF Core entity refresh

I would like if there is a way to trigger an object hierachy's lazy load after it has been added in the cache following an insert.
If I insert an object only providing it the foreign keys ids and try to request it right after, none of these properties / collections are retrieved.
Example:
_dbContext.Set<MYTYPE>().Add(myObject);
await _dbContext.SaveChangesAsync(ct);
_dbContext.Entry(myObject).Collection(c => c.SomeCollection).Load();
_dbContext.Entry(myObject).Reference(c => c.Property1).Load();
_dbContext.Entry(myObject.Property1).Reference(c => c.NestedProperty).Load();
_dbContext.Entry(myObject).Reference(c => c.Property2).Load();
_dbContext.Entry(myObject).Collection(c => c.SomeCollection2).Load();
_dbContext.Entry(myObject).Reference(c => c.Property3).Load();
_dbContext.Entry(myObject).Reference(c => c.Property4).Load();
_dbContext.Entry(myObject.Property4).Reference(c => c.SomeOtherNestedProperty).Load();
_dbContext.Entry(myObject).Reference(c => c.Property5).Load();
_dbContext.Entry(myObject).Reference(c => c.Property6).Load();
return _dbContext.Set<MYTYPE>().Where(x => x.Id == myObject.Id);
Unless I do all the Load() (having to nest into several layers sometimes ...), I can't have any of the sub properties properly lazy loaded.
Note: doing
_dbContext.Entry(myObject).Reload()
or
_dbContext.Entry(myObject).State = EntityState.Detached;
doesn't work.
Any idea? Because I have several cases like this, with HEAVY object nesting, and it feels really bad to have to do all the loads manually.
Thanks.
The thing is, if you try to add some line using only the integer as a foreign key like below :
db.tablename.Add(new tablename{
name = "hello",
FK_IdofanotherTable = 5
});
db.SaveChanges();
Then entity does not care about linking the object immediately, you won't be able to access the linked object as a property.
This is how it's actually done in modern Entity Framework :
var TheActualCompleteObject = db.AnotherTable.First(t => t.id == 5);
db.tablename.Add(new tablename{
name = "hello",
AnotherTable = TheActualCompleteObject
});
db.SaveChanges();
So... basically, you will have loaded the object anyway, it's not gonna make anything faster, but it will be more consistant. You will be able to navigate through foreign keys properties.
If this is not possible, then you will have to query back AFTER SaveChanges to retrieve your object AND have access to foreign properties (eagerly loading them, of course, or else it's the same disaster)
Exemple :
_dbContext.Configuration.LazyLoadingEnabled = false;
_dbContext.Set<MYTYPE>().Add(myObject);
await _dbContext.SaveChangesAsync(ct);
var v = _dbContext.First(t => t.id == myObject.id);
var fk_prop = v.Property1;
I also found no easy way around this.
The following might make it simpler...
public static void Reset(this DbContext context)
{
var entries = context.ChangeTracker
.Entries()
.Where(e => e.State != EntityState.Unchanged)
.ToArray();
foreach (var entry in entries)
{
switch (entry.State)
{
case EntityState.Modified:
entry.State = EntityState.Unchanged;
break;
case EntityState.Added:
entry.State = EntityState.Detached;
break;
case EntityState.Deleted:
entry.Reload();
break;
}
}
}
Hope this helps!
JS

Howto loop OrderedAssertions in FakeItEasy 2

As I understand, ordered assertions in FakeItEasy 2 are done like this (from the docs):
// Assert
A.CallTo(() => unitOfWorkFactory.BeginWork()).MustHaveHappened()
.Then(A.CallTo(() => usefulCollaborator.JustDoIt()).MustHaveHappened())
.Then(A.CallTo(() => unitOfWork.Dispose()).MustHaveHappened());
Now, suppose I have a collection and for each item in this collection I want to assert that a call was made to a faked object. What is the best approach to assert the calls were made in the correct order?
I came up with this, but don't really like it:
IOrderableCallAssertion ioca = null;
foreach (var item in items.OrderBy(i => i.Id)
{
var itemUnderTest = item;
if (ioca == null)
{
ioca = A.CallTo(() => fakeObject.Handle(itemUnderTest, otherArgument)).MustHaveHappened(Repeated.Exactly.Once);
}
else
{
ioca = ioca.Then(A.CallTo(() => fakeObject.Handle(itemUnderTest, otherArgument)).MustHaveHappened(Repeated.Exactly.Once));
}
}
That looks about right to me. Of course, you could inline itemUnderTest and pull MustHaveHappened outside of the two if branches.
And you could always hide this in a convenience method.
An alternative: use Invokes to capture the fakes as the calls come in and later compare them against a list.

T-SQL (Order by Case) to LinQ

I have the folowwing statement and I would like to have a LINQ equivalent:
SELECT *
FROM People
where Name like '%something%'
ORDER BY CASE
WHEN Name LIKE 'something%' then 1
WHEN Name LIKE '%something%' then 2
ELSE 3 END
Basically, I'm retrieving all the rows which contains a value (in this case 'something') and I'm ordering them: first the ones starting with that value, and then the remaining.
Any idea on how to do that in LinQ?
I've came out with the following solution.
var dc = new EntityContext();
var result = dc
// Condition part
.People.Where(x => x.Name.IndexOf("Foo") > -1) // This part is translated to like
// projection part
.Select(x => new { Person = x, Weight = x.Name.IndexOf("Bar") > -1 ? 1 : (x.Name.IndexOf("Baz") ? 2 : 0)})
// Order
.OrderBy(x => x.Weight)
// Final projection
.Select(x => x.Person);
I guess everything is self explanatory. First you select under your condition, then create a new object with weights necessary, then order it and finally take the necessary people.
I am not able to verify this, but something like this might work. The code can definitely be optimized/cleaned up, but in theory this just might work :)
The only question is whether the contains in the comparable delegate will translate the way it does in the Where. So, you might need to use an IndexOf or similar (as Oybek implemented)
var queryResult =
people
.Where(person=>person.name.Contains(#"/something/"))
.OrderBy(person=>person.Name,
delegate(string name1, string name2)
{
int result1, result2;
if(name1.Contains(#"something/")) result1 = 1;
else if(name1.Contains(#"/something/")) result1 = 2;
else result1 = 3;
if(name2.Contains(#"something/")) result2 = 1;
else if(name2.Contains(#"/something/")) result2 = 2;
else result2 = 3;
return result1.CompareTo(result2);
})

Use Take to including entity

I have two tables: categories and articles. One category can have many articles. I want to display overview of new articles separated by categories. But I want to display only first five articles per each category. How should I write query to solve this?
My first idea was something like this:
var cat = from c in ctx.categories
where c.IsPublic == true
select c;
But this contains all articles and I'm not sure how to write something like c.articles.Take(5) to query. Is it possible?
Oh, and it is ASP.NET MVC 2 with Entity Framework.
This worked for me. Important here is to switch off lazy loading to avoid that all articles are loaded when you iterate through the collection:
ctx.ContextOptions.LazyLoadingEnabled = false; // assuming you are in EF 4.0
var query = ctx.categories
.Where(c => c.IsPublic)
.Select(c => new {
Category = c,
Articles = c.Articles.Take(5)
}).ToList().Select(x => x.Category);
foreach (var category in query)
{
foreach (var article in category.Articles)
{
// runs only 5 times through this loop
}
}
Edit
If you don't dispose ctx and work with the context more after the code snippet above you probably better reenable lazy loading again ...
ctx.ContextOptions.LazyLoadingEnabled = true;
... before you get into trouble with other queries if your application mostly relies on lazy loading.
ctx.categories
.Where(c => c.IsPublic)
.Select(c => new
{
Category = c,
Articles = c.Articles.OrderBy(a => a.Whatever).Take(5)
).Select(c => c.Category);
Supposing that your class name is Category with an EntityCollection called Articles, this should work.