3

We are reviewing two different methods in generic repository patterns. Currently, want to map primary keys to Ids. The purpose of this is to map to the Generic Repository Interface which utilizes Id. Two solutions are provided below.

What are performance implications of .FindPrimaryKey().Properties. Does it cause a schema lock on database table in trying to find the primary key? Does it cause any application slowness?

How does it compare in performance vs Partial Class Method Solution 2? What option is better performance-wise?

Note: Architects demand the use of repository pattern at the workplace, so implementing it. Know there is debate surrounding this issue, but not my call.

Scaffolded Model Example:

namespace Datatest
{
    public partial class Property
    {
        public int Property { get; set; }
        public int DocumentId { get; set; }
        public string Address { get; set; }
    }
}

Sample Generic Base Repository for all tables:

    public T Get(int id)
    {
        return Table.Find(id);
    }
    public async Task<T> GetAsync(int id)
    {
        return await Table.FindAsync(id);
    }
    public T Single(Expression<Func<T, bool>> predicate)
    {
        return All.Single(predicate);
    }
    public async Task<T> SingleAsync(Expression<Func<T, bool>> predicate)
    {
        return await All.SingleAsync(predicate);
    }
    public T FirstOrDefault(int id)
    {
        return All.FirstOrDefault(CreateEqualityExpressionForId(id));
    }

Solution 1: FindPrimaryKey()

Generic Repository in C# Using Entity Framework

use EF FindPrimaryKey()

var idName = _context.Model.FindEntityType(typeof(TEntity))
        .FindPrimaryKey().Properties.Single().Name;

Solution 2: Partial classes Mapping

Net Core: Create Generic Repository Interface Id Mapping for All Tables Auto Code Generation

public partial class Property: IEntity
{
    [NotMapped]
    public int Id { get => PropertyId; set => PropertyId = value; }
}
jerrythomas38
  • 759
  • 2
  • 16
  • 41
  • Stop using Repository pattern with EF Core. Please! You are killing it! – Artur Aug 07 '19 at 04:33
  • hi @Artur Note: Architects demand use of repository pattern at workplace, so implementing it. Know there is debate surrounding this issue, but not my call, trying to make the best of it – jerrythomas38 Aug 07 '19 at 04:37
  • Try to show your old fashion architect how do you join 2 huge tables with and without repository pattern. In first case WHOLE data will be materialized and join will be performed in-memory killing your performance and years of work of db development and optimization. – Artur Aug 07 '19 at 04:46
  • 1
    anyways this is another topic, but appreciate the input, :) we already had a debate about this in the workplace they told us to move on – jerrythomas38 Aug 07 '19 at 04:56
  • It will help if you have foreign keys and navigation properties, but not in case of some random join. Anyway all these workarounds just increase complexity of the code and make new team member to spend a lot of time learning the wrapper aroung framework he is already familiar with. – Artur Aug 07 '19 at 05:03
  • I accept this. I was in same point like you and hated my architect :) – Artur Aug 07 '19 at 05:04
  • note the remarks on https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.metadata.internal.entitytype.findprimarykey?view=efcore-2.1#Microsoft_EntityFrameworkCore_Metadata_Internal_EntityType_FindPrimaryKey . Also, I'd expect this function to use reflection, so I'd assume to not perform particularly well either. However, since it works on EF's model, there should be no locks (or even calls) on the database during this procedure. – DevilSuichiro Aug 07 '19 at 06:34
  • hi @DevilSuichiro feel free to place in answer if needed, thanks – jerrythomas38 Aug 07 '19 at 06:44
  • hi @DevilSuichiro Ivan says You are looking at the wrong method documentation (the internal class implementation). The correct link is these are all public https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.metadata.ientitytype.findprimarykey?view=efcore-2.1#Microsoft_EntityFrameworkCore_Metadata_IEntityType_FindPrimaryKey – jerrythomas38 Aug 07 '19 at 07:01
  • @Artur and what do you suggest instead of using repository pattern with .NET Core EF ? What's the best approach to separate DAL and API or UI ? – Fourat Aug 07 '19 at 07:46
  • @Fourat let's call it 'Accessor' - the class that exposes bunch of useful methods to serve your businesses logic. This class internally will use `DbContext` as is, without additional wrappers, and will have access to all `DbSet`'s (aka tables) giving you ability to create complex Queries without fetching data too early. This class knows everything about Entity Framework but its consumers don't know what ORM, if any, is used inside. This way you take maximum of EF power while separating concerns. – Artur Aug 07 '19 at 08:08
  • @Artur I believe that's called old fashioned way (or DAO if you want). I don't think there's right or wrong about this subject and though many people say that we shouldn't implement Repository pattern with EF Code for various reasons, I still think it's a best practice but with a twist : Coupling the repository pattern with the unit of work pattern and taking advantage of the disposable pattern (already implemented in the `DbContext`). – Fourat Aug 07 '19 at 08:34

1 Answers1

4

Regarding the first approach (using EF Core metadata services):

First, EF Core is ORM (Object Relational Mapper), with most important here is Mapper.

Second, it uses the so called code based model, which means all the mappings are provided by code and not the actual database (even though the model is created by reverse engineering of an existing database).

In simple words, EF Core creates at runtime a memory data structure containing the information (metadata) about classes and properties, and their mappings to database tables, columns and relationships. All that information is based on pure code model - the entity classes, conventions, data annotations and fluent configuration.

All EF Core runtime behaviors are based on that metadata model. EF Core uses it internally when building queries, mapping the query results to objects, linking navigation properties, generating create/update/delete commands and their order of execution, updating temporary FK property values after getting the real autogenerated principal key values etc.

Hence the metadata model and discovering services (methods) use optimized data structures and are (has to be) quite efficient. And again, no database operations are involved.

So the first approach is quite efficient. The performance impact of obtaining the PK property name via metadata service is negligible compared to actual query building, execution and materialization.

Also the performance of the first approach is similar to EF Core Find method which you are using in another method. Note that when calling Find method you just pass the PK value(s) and not the properties. So the method implementation should somehow know how to build the Where expression, right? And what it does internally is very similar to the suggested snippet.


Regarding the second approach:

It's simply not comparable because it doesn't work. It's possible to use base class/interface, but only if the actual property name is mapped - like all classes have Id property, and it's mapped to different column name in the database tables using [Column] data annotation or HasColumnName fluent API.

In your example, the Id property is [NotMapped] (ignored). Which means EF Core cannot map to the table column. The fact that your are mapping it to another property via code (property getter/setter) doesn't matter. EF Core is not a (de)compiler, it can't see your code, hence cannot translate a LINQ query using such properties to SQL.

Which in EF Core 2.x leads to either client evaluation (very inefficient, reading to whole table and applying the filter in memory), or exception if client evaluation is configured to do so. And in EF Core 3.0+ it will always be an exception.

So in case you don't remove properties like PropertyId and map the property Id (which would be hard with "database first" models), the second "approach" should be avoided. And even if you can map the actual Id property, all you'll save would be a few milliseconds. And again, when using Find you don't bother about performance, why bother with methods that uses the same (or similar) approach.

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • 2
    The problem is that it does the "mapping" in code which EF Core (or any library) cannot "see". And yes, it causes client eval or exception. I've told you that in the very first comment on the other post. And read again the end of the answer - even though it technically "works" today - very inefficient and only if client eval is enabled, it really will stop working when you upgrade to EF Core 3.0+. So I don't know about your architect(s), but I'm considering it "not working". Even EF Core designers considered client eval to be a mistake, and decided to remove it in the next version. – Ivan Stoev Aug 07 '19 at 08:05
  • with your elaboration on the second approach, you are assuming that the TPC mapping has to remain with ignored properties, and Fluent API does not exist? – DevilSuichiro Aug 07 '19 at 21:13
  • @DevilSuichiro Not sure I follow. Fluent API or data annotations, ignored/unmapped properties cannot be used in L2E queries. My point was that in order to make #2 workable, the entities must use real property, eventually mapped to a specific column name. But may be I don't understand your question? – Ivan Stoev Aug 07 '19 at 21:19
  • hi Ivan, we are using Temporal Sys versioning tables, and making columns Not mapped, should we use make them, [DatabaseGenerated(DatabaseGeneratedOption.Computed)] instead? [NotMapped] public DateTime SysUtcstartTime { get; set; } [NotMapped] public DateTime SysUtcendTime { get; set; } –  Jun 28 '20 at 17:31
  • @AlanSmith5482 `[NotMapped]` columns are totally ignored by EF - they can't be be read, updated etc. Consider other options. – Ivan Stoev Jun 28 '20 at 18:46