-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Custom DB Functions for op_Equality are Ignored #28464
Comments
@robert-io We discussed this and we don't think this is a great idea. First, EF Core assumes that it understands the semantics of equality and this influences the shapes of queries generated. That is, equality is not just any other operation/function mapping, it is special. This is not something we will change since it would result in very poor generated queries if we cannot make this kind of assumption to simplify cases. EF Core also assumes equality semantics in change tracking and fixup. Beyond this, there doesn't seem much value in explicitly attempting to target the equality operator, as opposed to the Overall, the most reliable approach here would probably to create a user-defined function mapping in EF that could then be used by EF Core without it making any assumptions about the semantics of this function. |
@ajcvickers thanks for the response, i'll take a look at interceptors, with any luck they will provide what i need. For some background i am creating a custom data type and want to enable persisting to a data store like SQL Server, PostgreSQL, MongoDB etc whilst maintaining the same equality and comparison semantics in EF Core as we get in Linq to Objects. So finding a method that works transparently would be ideal. If to get the benefit consumers of this data type need to use a set of custom methods it makes my data type a second class citizen that requires special knowledge to benefit from it. What i'm thinking this would look like in the data store is a JSON object and a surrogate / computed field (using a type the data store understands) that provides equivalent equality and comparison semantics. |
@robert-io if you're simply representing some arbitrary .NET type as a database JSON type, then the LINQ equality operator simply gets translated as a SQL equality operator, which the database applies to the JSON values involved; I'm not sure why you'd need a custom equailty method here. Also note the work planned for JSON support in EF 7.0, which should make it possible to easily map any .NET type to JSON without any special work. More context on exactly why you think you need custom equailty may allow us to help you further. |
@roji the standard SQL equality is not going to work, it does not take into account the semantics of the data type, this will be a basic string comparison of the JSON and if the JSON fields are ordered different then quality will fail. My problem is more a limitation on data stores like SQL Server than a problem with EF Core, for most of the use cases i have the default equality is perfectly suitable. In my specific case i have created a type to represent Date & Time. The built in dotnet Now i want to be able to use my type as if it is supported directly by the underline data store e.g. SQL Server, MongoDB etc. This means queries like the following should just work var results = (
from p in db.Person
where p.BornOn == bornOn
select p
).ToList();
var results = (
from p in db.Person
where p.BornOn < someDateTime
select p
).ToList();
var results = (
from p in db.Person
where p.BornOn > someDateTime
select p
).ToList(); i don't want to use queries like: var results = (
from p in db.Person
where MyDateTimeType.Equals(p.BornOn, bornOn)
select p
).ToList();
var results = (
from p in db.Person
where MyDateTimeType.Compare(p.BornOn, someDateTime) < 0
select p
).ToList();
var results = (
from p in db.Person
where MyDateTimeType.Compare(p.BornOn, someDateTime) > 0
select p
).ToList(); When i build data access layers i make sure any And thanks for the JSON support in EF 7.0 heads up, i'll take a look. |
If database equality operation is not supported on a given type which is mapped to database type, then it is not "native" support in database. It is by default becomes second-class citizen in database. |
To add to @smitpatel's comment above...
That's certainly true, but what would you do instead? You could define a function which somehow reorders the entire JSON document string in the database, but that would likely be very expensive and cause database indexes to not get used.
I'd be interested in hearing the exact problem you have with DateTime/DateTimeOffset, and especially with NodaTime (I've worked quite a lot on mapping these). But in general, it's not really possible to define your own .NET type - including its own operations and semantics - and then expect that type to be mappable directly to your database... The database is a completely different environment, which knows nothing about either your type or its semantics. In other words, you generally have to adapt to the database's types and constraints, rather than expecting the database to adapt to yours.
This is specifically not something users should typically try to do. EF does attempt to translate LINQ queries so that they return the same results from the database as they would on in-memory collections, but there are some very important exceptions. One standard example is case-sensitive in string comparisons: while C# does case-sensitive matches, SQL Server is case-insensitive by default, and case-sensitivity is controlled via the column's collation rather than through the comparison operators/function as in .NET (the StringComparison parameter to string.Equals). There are some very good reasons for this discrepancy - databases typically manage lots of data, and indexes are crucial to querying speed, requiring the collation/case-sensitivity to be known in advance. To summarize, I'd recommend re-examining your approach to how you're trying to use your database, and to not attempt to impose 100% .NET-compliant behavior on your database. That's not possible in many cases, and in many cases where it is, would result in very significant performance problems. |
@roji well thats a few good questions, i'll try to answer what i can and i'll stick with SQL Server as the data store. What would i do instead of using
|
@robert-io thanks for the added details, here are a few comments. First, as I wrote above, work is underway on JSON support in EF 7.0; this should allow you to map a .NET POCO as a JSON document (e.g. to a SQL Server varchar column), and then generate queries with JSON_VALUE as you access members of that POCO in a LINQ query. I'm not 100% about datetime2 and CONVERT specifically, but that's definitely the general direction. But stepping back, I'm a little bit confused here... The above simply shows you putting a string representation of a datetime2 into a JSON document, but then converting that into a datetime2 whenever you're actually accessing that value (e.g. in WHERE/ORDER BY clauses). In other words, you're just using the standard, native database's datetime2 type, but rather than storing it directly as a column, you want to embed it in a JSON document. There's nothing wrong with that, but you certainly haven't "created a type to represent Date & Time", as you've written above. In other words, the database use its regular semantics and behavior around ordering, comparison, equality checks for datetime2 - there's no custom behavior here.
Collations in the database may vary in a very significant way from the way things work in .NET. SQL Server may happen to be a bit closer (not sure), but if you move to other databases things would probably become even more different.
So it seems that you're departing from regular .NET ways of doing things so that you can more closely model the database behavior, i.e. replace plain string properties with some other custom type just so it can sort of represent the database collation concept. I'm simply not sure why someone would go to great lengths for this, to the point of doing quite unnatural things on the .NET side. The point of LINQ is to allow expressing queries in a strongly-typed, C#-native way, not to have the same LINQ query execute 100% in exactly the same way across all possible LINQ providers. In short, I'd reconsider why exactly this is a goal for you; we usually see people going in this direction because they've decided to use in-memory testing and want the in-memory behavior correspond exactly to their database behavior. That is simply not possible to do for all cases, and we discourage this kind of testing approach (see docs). Other than testing, I really don't see why 100% identical behavior between in-memory and SQL behavior would be an absolute goal. |
Your right its not a new type in SQL Server but in Linq it'll be exposed as such, this is a pragmatic approach. I know i can't get exactly what i want because SQL Server doesn't support it. Once upon a time in SQL Server i could create CLR types but because that is not supported in AzureSQL (for reasonable security concerns) i'm not able to use them. Additionally its not an option for other data stores so i'm looking at ways to cover that gap. The goal of any ORM like EF Core is to reduce the gap between the underlying data store and the native programming experience, in this case C#, VB.Net and F#. With Date & Time essentially i need to store a 3 part tuple where the key data is the Local Time and Time Zone because UTC is a moving target relative to Local Time. I use the UTC value for filtering and ordering because that provides a consistent result accounting for different Time Zones. In a way i'm replicating the a STABLE function from PostgreSQL. Multiple times a year the Time Zone rules somewhere on Earth change and as a result the UTC time for a given Local Time is changed and when it does i have a service to correct the values in the DB. There are a few things that just won't map from my date & time type to some SQL expression such as functions that add/subtract time, convert to another time zone etc. Also i am in no way departing from the .NET way of doing things, C# is a general purpose Object Orientated programming language with the .NET Runtime providing key services. The .NET way is the Object Orientated way and defining types to encapsulate the behaviour you need is the Object Orientated way and is the .NET way. I like C# and for the most part i don't have a need to define new Value Types or similar because the .NET CLR/CTS provide almost everything i need / want in a well thought out way. |
For reference the |
We're very clear about EF not trying to make LINQ queries behave in exactly the same way across databases, or across your database and .NET. We of course do this by default where it makes sense, but reaching 100% is a very explicit non-goal which I believe is a misguided goal. Re your date & time, I don't disagree with storing local time, time zone, and UTC (see this post I wrote on this). The aspect I find problematic is trying to enclose the 3 values in a single .NET type, and then expecting to be able to seamlessly map that type to your database somehow, with all its semantic behavior. The easy, recommended way would simply to have three properties on your EF entity type - a local timestamp, a UTC timestamp, and a timezone string (note that the UTC timestamp can also be a computed column based on the other two). You can have a single POCO type on the .NET side that's constructed from these elements (e.g. via an unmapped property that's backed by the 3 mapped properties), but I don't think it's viable to try to map that POCO type directly to the database.
Of course that's a somewhat subjective matter. If you really are considering replacing your string properties with some other custom type in order to include a collation, so that they sort of correspond to a database textual column, I personally think that's departing from simple, standard .NET programming (and will complicate things). But of course if that works for you, then by all means. |
I'd advise looking again. PostgreSQL TIMESTAMP WITH TIME ZONE is nothing more than a timestamp that's UTC by convention, no actual time zone is stored in the database. If you need to store a timezone, you must have a separate string column to hold it. |
As a rule i agree, but this is a research project for me so i'm looking to find out how close i can get. The saying "perfect is the enemy of the good" is appropriate here. For any projects i have deliverables beyond my own curiosity i take a much more pragmatic approach and would not be trying this.
I like your post, it very much covers the issue i'm working on.
The .NET type only contains a I choose JSON to represent the data so it could be seamlessly mapped between the DB and the .NET type. This works where the value is not part of the This isn't a problem with ADO.Net and Dapper as the complexity can be abstracted away, but when you have an
If it was easy it wouldn't be worth doing. Broadly i think my goal is not achievable but it has been a great opportunity to learn about NodaTime and EF Core.
Good point, it still has the same issue though reading after the rules have changed and converting it to Local Time will not give you the correct result. |
For a research project, indeed it makes sense to see where one can go and explore atypical settings, and if you learned a lot from it, that's already a great thing. I'll just stress again that for a real/production system, I wouldn't go in this direction. I also wouldn't represent a complex type in JSON on the database side just so that it could be mapped to a single POCO on the .NET side - mapping to JSON introduce complexities and limitations, and I don't believe there's a justification for them for this kind of thing. It's really easy to do a classical mapping to 3 .NET properties; it may not be 100% how we'd like to model things OOP-wise, but that's the thing to do in the general case. |
Custom DB Functions for
op_Equality
are IgnoredWhen i register a Translation for a DB Function for the
op_Equality
method it is not used by EF Core when generating the SQL query.Given the
op_Equality
method is static i would expect the Translation function to be used when EF Core generates SQLThe following two Linq Queries should produce the same SQL but only the second works as expected:
The DbContext (this code is very much a work in progress, but does produce the SQL i expect):
The
DateTimeShell
class:The
Person
class:The table definition used by the above EF Core code:
Example JSON for the
[BornOn]
feild:Provider and Version Information
EF Core version: 6.0.7
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET 6.0
Operating system: Windows 11
IDE: Visual Studio 2022 17.3.0 preview 2.0
The text was updated successfully, but these errors were encountered: