How do I use Entity Framework to find a record in Cosmos with case insensitive conditions - entity-framework

I know tis question has been asked several times on SO but I haven't found a solution that works.
It's clear that Cosmos is capable of case insensitive searches since June 2020 but I haven't managed to find a solution that works with EF.
For example...
await objDbContext.Tags.SingleOrDefaultAsync(t => t.Value.Equals("value", StringComparison.InvariantCultureIgnoreCase))
... throws the following exception:
System.InvalidOperationException: 'The LINQ expression 'DbSet()
.Where(t => t.Value.Equals(
value: "Value",
comparisonType: InvariantCultureIgnoreCase))' could not be translated. Additional information: Translation of the 'string.Equals' overload with a 'StringComparison' parameter is not supported. See https://go.microsoft.com/fwlink/?linkid=2129535 for more information. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.'
I've also tried .ToLower and string.Equals with similar results.
Is there a right way to do it? If not, is there an alternative? I'm happy to store the value in lowercase along side the actual value, to be used for searching only, but it must be updated automatically.

EF Core 5 has now implemented the transactions. Once you have updated you can change this line
await objDbContext.Tags.SingleOrDefaultAsync(t => t.Value.Equals("value", StringComparison.InvariantCultureIgnoreCase));
to
await objDbContext.Tags.SingleOrDefaultAsync(t => t.Value.ToLower() == "value");

Related

Case-insensitive ValueComparer doesn't seem to work with Find on untracked entity

I have set a case-insensitive ValueComparer<string> for the primary key, but when calling Find() it returns null if the entity isn't being tracked.
The configuration of the property and its value comparer:
modelBuilder.Entity<ProductEntity>().HasKey(e => e.Name);
modelBuilder.Entity<ProductEntity>(e =>
{
e.Property(p => p.Name).Metadata.SetValueComparer(new ValueComparer<string>(
(s1, s2) => string.Equals(s1, s2, StringComparison.OrdinalIgnoreCase),
s => s.ToUpper().GetHashCode(),
s => s
));
});
Code example to highlight the problem:
var builder = new DbContextOptionsBuilder<ShoppingListContext>();
builder.UseSqlite(ShoppingListContextFactory.SqliteConn.Value);
var contextFirst = new ShoppingListContext(builder.Options);
contextFirst.Database.EnsureCreated();
contextFirst.Products.Add(new ProductEntity { Name = "Apple" });
contextFirst.SaveChanges();
var contextSecond = new ShoppingListContext(builder.Options);
Console.WriteLine(contextFirst.Products.Find("apple") is null);
Console.WriteLine(contextSecond.Products.Find("apple") is null);
Outputs:
False
True
I would suspect this is by design. The ValueComparer is used internally within EF for change tracking, essentially when working with entities that are already loaded and tracked where certain data types or rules around what constitutes a change would not be otherwise detected. Where a Database is normally case-insensitive, EF would normally detect changing "Atlanta" to "atlanta" as a valid change, where you might not want to actually trigger an UPDATE in that case since it wouldn't matter as far as the database is concerned. The side effect is that fetching data from the change tracking cache may use the configured value comparer, that doesn't automatically flow through to generated SQL commands. If your database collation is case-sensitive then your querying against string values will need to accommodate for that.
Forcing case insensitivity against a database that is case sensitive needs to be done carefully since indexing on the database may or may not match what you are configuring your Linq Queries to use which can have significant performance implications.

LINQ InvalidOperationException when calling ToList()

I want to update records in a table in my database using the code below:
context.Events
.Where(eventDb => !events.Any(#event => eventDb.Guid == #event.Guid))
.ToList()
.ForEach(eventDb => eventDb.IsCurrent = false);
But every time on calling ToList() I'm getting the following error, which isn't clear to me:
System.InvalidOperationException: The LINQ expression 'event => EntityShaperExpression:
WawAPI.Models.Event
ValueBufferExpression:
ProjectionBindingExpression: EmptyProjectionMember
IsNullable: False
.Guid == event.Guid' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'.
I'm not sure what I'm doing wrong and I'd appreciate any help.
I think the problem is that you are asking the SQL server to perform a .Any() operation on events which is presumably some sort of in-memory C# object and does not exist on the SQL server and is too complicated to be sent to SQL.
If you extract the Guids from events into a simple list of strings, then that could be simple enough to send to sql. You also need to change your linq query slightly, but I think this will give you the results you are looking for.
var eventGuids = events.select(a=>a.Guid).ToList();
context.Events
.Where(eventDb => !eventGuids.Contains(event.Guid))
.ToList()
.ForEach(eventDb => eventDb.IsCurrent = false);
I think the issue is that EventDb.Guid == #event.Guid can't be converted to the database raw SQL.
What type are the Guid's?
See what happens if you move the ToList() to before the Where clause.

Performance issue with fluent query in EF vs SharpRepository

I was having some performance issues using SharpRepository, and after playing around the SQL Query Profiler I found the reason.
With EF I can do stuff like this:
var books = db.Books.Where(item => item.Year == '2016');
if (!string.IsNullorEmpty(search_author))
books = books.Where(item => item.Author.Contains(search_author);
return (books.ToList());
EF will not really do anything until books is used (last line) and then it will compile a query that will select only the small set of data matching year and author from the db.
But SharpRepository evaluates books at once, so this:
var books = book_repo.Books.FindAll(item => item.Year == '2016');
if (!string.IsNullorEmpty(search_author))
books = books.Where(item => item.Author.Contains(search_author);
return (books.ToList());
will compile a query like "select * from Books where Year == '2016'" at the first line, and get ALL those records from the database! Then at the second line it will make a search for the author within the C# code... That behaviour can be a major difference in performance when using large databases, and it explains why my queries timed out...
I tried using repo.GetAll().Where() instead of repo.FindAll().... but it worked the same way.
Am I misunderstanding something here, and is there a way around this issue?
You can use repo.AsQueryable() but by doing that you lose some of the functionality that SharpRepository can provide, like caching or and aspects/hooks you are using. It basically takes you out of the generic repo layer and lets you use the underlying LINQ provider. It has it's benefits for sure but in your case you can just build the Predicate conditionally and pass that in to the FindAll method.
You can do this by building an Expression predicate or using Specifications. Working with the Linq expressions does not always feel clean, but you can do it. Or you can use the Specification pattern built into SharpRepository.
ISpecification<Book> spec = new Specification<Book>(x => x.Year == 2016);
if (!string.IsNullorEmpty(search_author))
{
spec = spec.And(x => x.Author.Contains(search_author));
}
return repo.FindAll(spec);
For more info on Specifications you can look here: https://github.com/SharpRepository/SharpRepository/blob/develop/SharpRepository.Samples/HowToUseSpecifications.cs
Ivan Stoev provided this answer:
"The problem is that most of the repository methods return IEnumerable. Try repo.AsQueryable(). "

How to run dynamic SQL query in Entity Framework 7 that auto-maps to Entities (SqlQuery<T>)?

I have an existing app that I am trying to upgrade from MVC5/EF6 to MVC6/EF7. We dynamically create some of our SQL tables, and as a result, have made use of the
System.Data.Entity.Database.SqlQuery
method to automatically map to entities that we use throughout our application.
This method seems to have gone away (i.e. not part of
Microsoft.Data.Entity.Infrastructure.Database ) in EF7 (or is not yet implemented). Are there plans to re-implement this method in EF7 or is there another way to accomplish this? Our project is kind of dead in the water until we figure this out.
Edited on May 20, 2015
I've been trying to make this work with FromSql, since that's what's available in Beta4, but no matter what combination of concatenated string, parameters I try, I keep getting different versions of an "Incorrect Syntax near #xxxvariable" message.
var results = Set<AssessmentResult>().FromSql("dbo.GetAssessmentResults #FieldA='Data1', #FieldB='Data2', #UserId = 2303");
var results2 = Set<AssessmentResult>().FromSql("dbo.GetAssessmentResults #FieldA= {0}", intData);
Both of these calls result in
"Incorrect syntax near '#FieldA'"
Any ideas?
We recently introduced the .FromSql() extension method on DbSet. It has the added benefit that you can continue composing LINQ on top of it.
var customers = db.Customers
.FromSql("SELECT * FROM Customer")
.Where(c => c.Name.StartsWith("A"));

Exception in Expression Trees

This is my model:
- Business
- BusinesType - FK
- Categories (*) - FK
- Branch (*)
- BranchType - FK
- Address
- Phone (*)
- CustomFields (*)
- OpeningTimes (*)
- WorkingPeriods (*)
- .....
Now I have a controller-action that accepts a form that consists of the whole bunch of data as a single Business entity with all its properties and collections set fine.
Now I have to walk thru all the properties and collections recursively, and compare with the database graph; if they don't exist, add them, if they do walk thru all properties again and perform the same to a deeper level until no navigation properties are left over. Since I have many more properties and descendants than mentioned in the previous example, it's just inside to walk thru them manually.
Thanks to this answer I found GraphDiff which offered a brilliant solution to the situation.
Here's an update query I'm calling:
Context.UpdateGraph(business, bus => bus
.AssociatedEntity(bu => bu.BusinessType)
.AssociatedCollection(bu => bu.Categories)
.OwnedCollection(bu => bu.Branches, branch => branch
.AssociatedEntity(b => b.BranchType)
.OwnedEntity(b => b.Address)
.OwnedCollection(b => b.Phones)
.OwnedCollection(b => b.CustomFields)
.OwnedCollection(b => b.OpeningTimes, openingTimes => openingTimes
.OwnedCollection(b => b.WorkingPeriods)
)
)
);
It throws this exception:
System.InvalidCastException: Unable to cast object of type 'System.Linq.Expressions.MethodCallExpressionN' to type 'System.Linq.Expressions.MemberExpression'.
I tried debugging the source code, but I'm not an expert with Expression Trees, the problem occurs when the internal Include call (to include object graph to load store object) tries to attach WorkingPeriods, looks like it's not ready to take that deepness level of recursion. I messed around with it a bit, but I'm sure someone with extensive knowledge in expression trees will be able to solve this easily. Any suggestions will be appreciated on that to.
Here's what the include path expression is supposed to be generated like:
.Include(b =>
b.Branches.Select(br =>
br.OpeningTimes.Select(ot =>
ot.WorkingPeriods)));
Here's the stacktrace of the error.
Essentially, the exception is thrown because the recursive call returns the inner include as a method call, without processing it and returning the collection property it's meant to expose.
sorry it took me a while to get back to you.
I'ts 3am and I've had a fair bit of wine but the problem is fixed :) If you get the latest version of code # https://github.com/refactorthis/GraphDiff it should work fine.
I'll update the new nuget package (RefactorThis.GraphDiff) soon.