dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.81k stars 3.2k forks source link

Custom Value Generator Which is trying to acces DI Service failed #10792

Open SPEMoorthy opened 6 years ago

SPEMoorthy commented 6 years ago

Hi, I am trying to implement a Custom Value generator. Whchi is getting number from other DB.

My code is as follows.

public class MyValueGenerator: ValueGenerator<int>
    {
        private ISeqDefinitionRepository _repository;

        public MyValueGenerator(ISeqDefinitionRepository repository)
        {
            _repository = repository;
        }

        public override bool GeneratesTemporaryValues => false;

        public override int Next(EntityEntry entry)
        {
            if (entry == null)
            {
                throw new ArgumentNullException(nameof(entry));
            }

            int res = _repository.NextValue(1);
            return res; 
        }
    }

On Model Creating

 private void ConfigureMfr(EntityTypeBuilder<Mfr> builder)
        {
            builder.Property(b => b.MfrId).HasValueGenerator<MyValueGenerator>();
        }

I am getting following Error

Cannot create instance of value generator type 'MyValueGenerator'. Ensure that the type is instantiable and has a parameterless constructor, or use the overload of HasValueGenerator that accepts a delegate. No parameterless constructor defined for this object.

Requesting to guide met to get the instance of the service in custom ValueGenerator Class.

Further technical details

EF Core version: 2.0 Database Provider: Microsoft.EntityFrameworkCore.SqlServer Operating system: Visual Studio 2017

Thanks & Regards, Easwar

ajcvickers commented 6 years ago

Notes from triage: It's not unreasonable to attempt to resolve these from D.I., so putting it in the backlog for consideration as a future enhancement.

@SPEmoorthy You posted and then deleted a workaround--was there a reason for deleting the workaround that you posted?

SPEMoorthy commented 6 years ago

@ajcvickers I was trying to Resolve the sevice from DBContext objet. The Service collection returned from DBContext is internel to DBContext's DI Container. I realised that later :-(. So Deleted that workaround. Actuly I am trying to get the Id generated from outside of the application. Id's are maintained in Separate DB. That DB is consumed by Many application. Current project requires to get the generated number from that db. I just tried to get the ID from Custom ValueGenerator. Right Now Managing without DI.

    public override int Next(EntityEntry entry)
    {
        if (entry == null)
        {
            throw new ArgumentNullException(nameof(entry));
        }
        //_repository = entry.Context.GetInfrastructure().GetService<ISeqDefinitionRepository>()  //Not Working          
       _repository = new SeqDefinitionRepository((MyDbContext)entry.Context); //Managed Without DI. Now in same db
        return _repository .NextValue(1);     
    }

I hope The Id Generation is not always happening in DB. It May happen in Outside of the application scope. (Docoumt number in Enterprises is globally unique in nature, Separate application is handling that part). If we can tieup the applcation services in ValueGenerator, will be an added advantage.

ajcvickers commented 6 years ago

@SPEmoorthy Thanks for the additional info.

fairking commented 6 years ago

It would be nice to have that feature in EF Core. I got some problem with persisting dates based on the user timezone which is a scoped service. Really need some DI for ValueGenerator and ValueConverter.

shupoval commented 5 years ago

Another use case for this feature is getting a value of currently logged in user based on e.g. IHttpContextAccessor

taconaut commented 5 years ago

FYI, I've been able to access a service registered for scope using var userProvider = entry.Context.GetService<ITeamTunerUserProvider>();

jhonnyelhelou91 commented 5 years ago

It worked for me the same way @taconaut was proposing. You can't include the repository in the constructor of your custom value generator since the injectable type is not listed in the registered services for the service provider. Below are the classes I used to make it work.

UserGenerator.txt IUserProvider.txt TestDbContext.txt Program.txt

eation commented 4 years ago

My code

services.TryAddSingleton<IMyLongIDGenerator, MyLongIDGenerator>();

public class MylongValueGenerator : ValueGenerator<long>
{
     //......
    public override long Next([NotNull] EntityEntry entry)
    {
        if (entry == null)
        {
            throw new ArgumentNullException(nameof(entry));
        }
        var gen = entry.Context.GetService<IMyLongIDGenerator>();
        return gen.NextValue();
    }
}

// for some single DbContext ID generator
services.AddDbContext<DBContext>(options =>{
                options.ReplaceService<IValueGeneratorSelector, MyValueGeneratorSelector>();
            };
//Or for global
//services.TryAddSingleton<IValueGeneratorSelector, MyValueGeneratorSelector>();

public class MyValueGeneratorSelector : ValueGeneratorSelector
{
    //......
    public override ValueGenerator Create(IProperty property, IEntityType entityType)
    {
        //......
        if (property.ValueGenerated != ValueGenerated.Never)
        {
            if (propertyType == typeof(long))
            {
                return new MylongValueGenerator(false);
            }
            //......
        }
    }
}

it work for EF Core version: 3.0/3.0.1