Skip to content
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

Can ValueGenerator be applied in add and update situation? #6999

Open
JonPSmith opened this issue Nov 11, 2016 · 11 comments
Open

Can ValueGenerator be applied in add and update situation? #6999

JonPSmith opened this issue Nov 11, 2016 · 11 comments

Comments

@JonPSmith
Copy link

JonPSmith commented Nov 11, 2016

Steps to reproduce

Summary: I am trying to understand the ValueGenerator | .HasValueGenerator feature. To do so I
am trying to create a simple tracking example where I saved the username of the person that created, and the username of person that updated a class.

The issue

It looks like the ValueGenerator is only called on an add. Is that correct?

I can of course override SaveChanges\Async and get at the ChangeTracker to do this (which is what I do in EF6), but I wondered if there was a better way in EF Core.

For your laughter here is my code:

internal class SimpleTrackingConfig<T> where T : class, ISimpleTracking
{
    private readonly UserNameValueGenerator _valueGenerator;
    public SimpleTrackingConfig(Func<string> getCurrentUser)
    {
        _valueGenerator = new UserNameValueGenerator(getCurrentUser);
    }

    public void Configure(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<T>()
            .Property(x => x.CreatedOn)
            .HasDefaultValueSql("getutcdate()");

        modelBuilder.Entity<T>()
            .Property(x => x.CreatedBy)
            .HasMaxLength(256)
            .HasValueGenerator((p, e) => UserNameFactory());

        modelBuilder.Entity<T>()
            .Property(x => x.UpdatedOn)
            .HasComputedColumnSql("getutcdate()");

        modelBuilder.Entity<T>()
            .Property(x => x.UpdatedBy)
            .HasMaxLength(256)
            .HasValueGenerator((p, e) => UserNameFactory());
    }

    private ValueGenerator<string> UserNameFactory()
    {
        return _valueGenerator;
    }

    private class UserNameValueGenerator : ValueGenerator<string>
    {
        private readonly Func<string> _getCurrentUser;

        public UserNameValueGenerator(Func<string> getCurrentUser)
        {
            _getCurrentUser = getCurrentUser;
        }

        public override bool GeneratesTemporaryValues => false;

        public override string Next(EntityEntry entry)
        {
            return _getCurrentUser == null ? "" : _getCurrentUser();
        }
    }
}

Further technical details

EF Core version: "Microsoft.EntityFrameworkCore.SqlServer": "1.1.0-preview1-final"
Operating system: Windows 10
Visual Studio version: VS2015 update 3

Other details about my project setup:

@ajcvickers
Copy link
Contributor

@JonPSmith Value generators are only invoked when adding a new entity (setting its state to Added) and the property value is the CLR default. We have talked about allowing them to be invoked at other times, but we haven't made any plans to do so.

@JonPSmith
Copy link
Author

Thanks @ajcvickers. All goes to my understanding of EF Core. I may change my example for the book as I'm not sure I'm using Value generators in the way you intended. From looking more at your tests it looks like you designed Value Generators to create keys.

It was a good idea while it lasted 😊

@poke
Copy link

poke commented May 2, 2019

I would like to ask for this functionality for updates as well.

I have an entity that has a Name and a NormalizedName which is normalized based on the Name using a fixed rule. This makes it possible to have NormalizedName as an index and look up entities based on it. It works equialent to ASP.NET Core Identity’s NormalizedUserName and NormalizedEmail.

Now, I would like to use a value generator to make sure that this normalized column is always calculated properly on top off the current Name value. Identity solves this by basically updating it on every change through the UserManager, but I would like to move this to the database if possible.

Using a ValueGenerator already allows me to do this on adds; but I would like to have this run on updates too, so I don’t have to go through higher-level services just to enforce this.

@smitpatel
Copy link
Contributor

Would HasComputedColumnSql work in that case?

@ajcvickers
Copy link
Contributor

See also discussion here: #19765

@eegeeZA
Copy link

eegeeZA commented Apr 22, 2020

Added a workaround: #19765 (comment)

@JonPSmith
Copy link
Author

Hi @smitpatel,

I tried entity.Property(p => p.LastUpdated).HasComputedColumnSql("getutcdate()", stored: true) and SQL Server said "Computed column 'LastUpdated' in table 'Persons' cannot be persisted because the column is non-deterministic."

@Dzivo

This comment was marked as spam.

@roji

This comment was marked as spam.

@thereelaristotle

This comment was marked as spam.

@roji

This comment was marked as spam.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
10 participants