Skip to content

Is there an option to extend the model without inheriting from DbContext? #32462

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Closed
voroninp opened this issue Nov 30, 2023 · 17 comments
Closed

Comments

@voroninp
Copy link

For example, I am developing a NuGet library which should support various DB libraries including EF.

Is there any option to extend data model other than requiring DbContext to implement a particular interface or inherit from another context?

@roji
Copy link
Member

roji commented Nov 30, 2023

Your nuget package can integrate into the model-building process by introducing its own conventions; as an example, see EFCore.CheckConstraints which adds various check constraints to the model.

To help out more, we'd need to know exactly what kind of model changes your library intends to do etc.

@voroninp
Copy link
Author

Very often, I need to extend entities with service properties like Timestamp or Version and manage such properties automatically.

For example, Version can be a (shadow) property which CurrentValue is incremented right before the changes are saved to DB.

@voroninp
Copy link
Author

Or I just need to add some table for library-specific data. Like Outbox for published messages.

@roji
Copy link
Member

roji commented Nov 30, 2023

Then yeah, plugin conventions seem like the thing to do - take a look at EFCore.CheckConstraints.

@voroninp
Copy link
Author

Ok, thanks, I'll look at it.

And if I want to intercept expression tree manipulation?

As I said before, I'd like to express domain behavior in methods which work exclusively with entity's state. EF cannot translate these methods to SQL, but if method call could be replaced with its body, EF will do fine.

So I want an extension coupled with source generator.

public sealed class Foo
{
    public int Prop1 {get; set;}
    public string Prop2 {get; set;}

    public bool IsBar() => Prop1 == 0 && Prop2 == "bar";
}

If I call

ctx.Set<Foo>().Where(f => f.IsBar()).ToList()`

EF will understandably throw.

So, my idea is to have a source generator which checks for instance method calls and creates expression equivalents:

Expression<Func<Foo, bool>> isBarExpr = foo => Prop1 == 0 && Prop2 == "bar";

The second step would be to replace Where(() => f.IsBar()) with Where(isBarExpr).

It can be done without source generator if explicit mapping between the method and expression is provided.

@roji
Copy link
Member

roji commented Nov 30, 2023

A plugin can also add method and member translators - you can look at the NodaTime and/or NetTopologySuite plugins in this repo for inspiration.

I'll go ahead and close this now as the questions have been answered, but feel free to post back here if you need to.

@roji roji closed this as not planned Won't fix, can't repro, duplicate, stale Nov 30, 2023
@voroninp
Copy link
Author

voroninp commented Mar 18, 2024

@roji Q1: Do I get it right that annotations is the way to extend configuration of entites?

For example, I want to mark a property as a timestamp.

b.Property(e => e.TimestampUtc).IsTimestamp();
public static PropertyBulder<DateTime> IsTimestamp(this PropertyBulder<DateTime> propertyBuilder, bool isTimeStamp = true)
{
    return propertyBuilder.HasAnnotation("voroninp:IsTimeStamp", isTimeStamp);
}

@roji
Copy link
Member

roji commented Mar 18, 2024

@voroninp in general yes, but I'd need to know more about exactly what you're trying to achieve.

@voroninp
Copy link
Author

@roji

  1. Mark a property to work as a timestamp. Either with attribute or with fluent mapping.
  2. When DbContext performs SaveChanges, scan through the entities (Added/Modified), find those with timestamp properties and assign new value to the property.
  3. The value should be acquired via TimeProvider which is injected to the service which does the job of updating the property.

@roji
Copy link
Member

roji commented Mar 18, 2024

Sounds like you want to set a value generator that works on update (not yet supported, #6999). Until then, although annotations are meant more as an internal and provider thing, you may be able to use that as a workaround, yes. Another option is to get the timestamp generated in the databases e.g. via a trigger (doc). /cc @ajcvickers in case he has other ideas.

@voroninp
Copy link
Author

Well, currently I can just override SaveChanges and do the work there. But I'd like to have it as an extension. Currently not possible?

@voroninp
Copy link
Author

Also, how can I add annotation to the entity via PropertyBuilder? For example, I want entity to be annotated with voroininp:HasTimestampProperty when I call .IsTimestamp().

@roji
Copy link
Member

roji commented Mar 18, 2024

Not sure what you're asking - you seem to already be using PropertyBuilder.HasAnnotation() - IsTimestamp() can be an extension over PropertyBuilder which just does that, no?

@voroninp
Copy link
Author

I mean, I add annotation to the property, but I'd like to add it to the entity as well when I call IsTimestamp()

@roji
Copy link
Member

roji commented Mar 18, 2024

You can drop down to the lower-level metadata API and get the property's declaring entity type:

modelBuilder.Entity<Blog>().Property(f => f.Id).Metadata.DeclaringType.SetAnnotation();

@voroninp
Copy link
Author

Ok, thanks.
Next question.
Which service do I need to extend/replace to intercept saving changes?

@voroninp
Copy link
Author

ping...

# for free to join this conversation on GitHub. Already have an account? # to comment
Projects
None yet
Development

No branches or pull requests

2 participants