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

Flexible mapping to CLR types and members (Custom O/C Mapping) #240

Open
rowanmiller opened this issue May 22, 2014 · 35 comments

Comments

Projects
None yet
@rowanmiller
Copy link
Member

commented May 22, 2014

Although we have had support for POCOs through a few major versions, using EF for persistence still places some undesired constraints on the design choices developers can make on their domain objects.

E.g, EF Core still requires that mapped CLR types can be instantiated through constructors without parameters, that they contain both property getters and setters for each mapped scalar and reference property, and that all mapped collection navigation properties are exposed as properties that are of a type that implements ICollection.

EF Core also requires each entity type in the model to be mapped to a distinct CLR type, which makes some dynamic model scenarios (#2282) harder.

Moreover, scalar properties on objects have to be of a small set of recognized types in order to be mapped (in EF Core the set of types supported natively by the provider).

Richer constructs such as collections of scalars or collections of complex types, ordered collections, inheritance in complex types, collection manipulation methods, factory methods, and immutable objects are not supported.

This issue tracks the removal of those constrains as a whole and serves as a parent issue for some individual features that we will track independently:

  • #246 Complex types or value objects support
  • #9630 Support inheritance for owned types
  • #752 Custom collection patterns
  • #4179 Collections of scalar types
  • #2919 Richer collection support
  • #2968 Custom property access patterns
  • #9914 Shared-Type Entities
  • #242 Simple data store to CLR conversions (custom type mapping)
  • #3342 Flexible construction through factory methods and DI
  • #12078 Allow related entities to be passed to constructor of aggregate root
@rowanmiller

This comment has been minimized.

Copy link
Member Author

commented Nov 10, 2014

Here is a good example of this feature (good case to verify against once we implement it) - #1009

@divega

This comment has been minimized.

Copy link
Member

commented Sep 1, 2015

@rowanmiller Looking at this it seems that the bug I filed yesterday is more specific about property access while this is the all-encompassing flexible mapping. Would you mind if I just add #2968 as a child, similar to how #857 is a child specific to collection patterns?

@rowanmiller

This comment has been minimized.

Copy link
Member Author

commented Sep 1, 2015

@divega yep that works

@divega

This comment has been minimized.

Copy link
Member

commented Sep 1, 2015

Otherwise I can merge it all here...

@divega divega changed the title Flexible mapping to CLR types/properties/methods/etc. (O/C Mapping) Flexible object mapping to CLR types and members (Custom O/C Mapping) Sep 1, 2015

@divega

This comment has been minimized.

Copy link
Member

commented Sep 1, 2015

Made some edits to the original text of the issue to add background and additional links to related issues.

@biqas

This comment has been minimized.

Copy link

commented Mar 8, 2016

Hi,
the following example is for Illustration how maybe interface definition could be used to map EF structures.

public class BloggingContext : DbContext
{
    // Type mapping for materialization
    public DbSet<IBlog, Blog> Blogs { get; set; }

    // Materialization can create type on the fly because no specific type was given. (Generators or emitting)
    public DbSet<IPost> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<IBlog>()
            .Property(b => b.Url);

        // Optional: provide type creation factory.
        modelBuilder.Entity<IBlog>(() => new Blog());

        // Optional: provide collection behavior if non is provided then use default or emitt or generate.
        modelBuilder.Collection<IList<Any>>((IList<Any> collection, Any item) => collection.Add(item));
    }
}

// Any represents all kind of types which can be used in a collection.
class Any { }

public class DbSet<T, TEntity> where TEntity : class, T { }

public interface IBlog
{
    int BlogId { get; set; }

    string Url { get; set; }

    IList<IPost> Posts { get; set; }
}

public class Blog : IBlog
{
    public int BlogId { get; set; }

    public string Url { get; set; }

    public IList<IPost> Posts { get; set; }
}

public interface IPost
{
    int PostId { get; set; }

    string Title { get; set; }

    string Content { get; set; }

    int BlogId { get; set; }

    IBlog Blog { get; set; }
}

public class Post : IPost
{
    public int PostId { get; set; }

    public string Title { get; set; }

    public string Content { get; set; }

    public int BlogId { get; set; }

    public IBlog Blog { get; set; }
}

I know there are lot of other scenarios, so if someone has specific questions how for example x,y and z would fit in this kind of mapping, please ask, I will try to provide examples and explanations.

@bjorn-ali-goransson

This comment has been minimized.

Copy link

commented Jul 30, 2016

Actually, todays code base is quite close to being able to handle this. It just needs to be 'laxed in some parts. I mean the IClrPropertyGetter/IClrPropertySetter infrastructure is already there.

I got stuck at the type check in EntityType.AddProperty, I dunno if it would have worked otherwise (probably not...)

We need to be able to specify, when adding a Property:

  1. Clr type (not needed in practice as PropertyInfo.SetValue takes object)
  2. Resulting primitive backing type (for DB provider)
  3. Getter Serializer (converts from Clr type to Resulting primitive type)
  4. Setter Parser (converts from Resulting primitive type to Clr type)

And the type check mentioned above needs to not happen if(Flexible).

Here's a (hopefully somewhat) functioning example of what I'd like to achieve:

public class Context : DbContext
{
    private static readonly IServiceProvider _serviceProvider = new ServiceCollection()
        .AddEntityFrameworkSqlServer()
        .AddSingleton<ICoreConventionSetBuilder, MyCoreConventionSetBuilder>()
        .BuildServiceProvider();

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseInternalServiceProvider(_serviceProvider)
            .UseSqlServer(@"Data Source=.\SQLEXPRESS2014;Initial Catalog=EfCoreTest;Integrated Security=True;");
    }

    private class MyCoreConventionSetBuilder : CoreConventionSetBuilder
    {
        public override ConventionSet CreateConventionSet()
        {
            var value = base.CreateConventionSet();
            value.EntityTypeAddedConventions.Add(new MyPropertyDiscoveryConvention());
            return value;
        }
    }

    private class MyPropertyDiscoveryConvention : IEntityTypeConvention
    {
        public InternalEntityTypeBuilder Apply(InternalEntityTypeBuilder entityTypeBuilder)
        {
            entityTypeBuilder.Metadata.AddProperty(
                "Ancestors",
                propertyType: typeof(string),
                flexible: true,
                serialize: JsonConvert.Serialize,
                parse: value => JsonConvert.Parse<List<int>>((string)value)
            );

            return entityTypeBuilder;
        }
    }

    public DbSet<Page> Pages { get; set; }

    public class Page
    {
        public int Id { get; set; }
        public List<int> Ancestors { get; set; }
    }
}
@bjorn-ali-goransson

This comment has been minimized.

Copy link

commented Aug 5, 2016

@rowanmiller any thoughts or progress on this? I really think that just relaxing the checks a bit on this (and maybe generalizing the structure of Property a little) would get us 80% there.

( @divega )

Also, FWIW, I now think that we would be better off with a new AddFlexibleProperty method rather than further overloading the AddProperty.

@bjorn-ali-goransson

This comment has been minimized.

Copy link

commented Jan 10, 2017

Since this doesn't seem to be on the roadmap, I'd like to stop waiting and ask is this functionality available in any other framework?

EF6? (rather not but ...)
Maybe Hibernate.NET?

@marchy

This comment has been minimized.

Copy link

commented Jan 24, 2017

Guys this should be prioritized.
The whole point of an ORM is to Map to Objects... and EF has traditionally stood out particularly due to its ability to map to POCOs / domain representations of objects.

This means removing the friction of relational mapping and the ability to hide the complexity. Today there is still quite a LOT of friction in this and the reality is quite a ways from the promise/vision.

I know the team's been really focused on re-creating EF Core for modern computing paradigms (cloud, lightweight client, mobile – still missing, though really who cares – Realm has you beat)... but I feel it's really time to get back to new feature growth as opposed to catch-up/parity with EF6.

EF's biggest benefits are that of productivity by removing the object-relational friction / impedance mismatch and custom mapping would allow the framework to much better deliver on its promise.

Would love to see the team bite off this big, ambitious chunk and put the framework on an exciting path against the alternatives!

#keepupthegreatwork

@hidegh

This comment has been minimized.

Copy link

commented Feb 15, 2017

hope flexible mapping also includes the possibility to totally bypass (turn off, remove) the property auto mapping feature. is this possible at the current stage?

if i'm having an o-o class and i'm extending it with properties i don't wanna write to DB, i need to go tot the mapping class and list there a property i don't want to map just to get it ignored.

@marchy

This comment has been minimized.

Copy link

commented Feb 17, 2017

@hidegh Agreed that would be super helpful. With the amount of limitations to EF object mapping we have a slew of properties outside of what's persisted. Would be much better to opt in to persisting them than opt out for everything else.

On that note, computed/get-only properties should not need to ever be explicitly ignored - by definition they can never persist lol. This should be automatically ignored by EF but right now you have to specify to ignore those too.

@ajcvickers

This comment has been minimized.

Copy link
Member

commented Feb 17, 2017

@marchy If you are finding read-only properties (i.e. properties without a setter) being mapped automatically, then can you please file a new issue with a repro. That should not be happening.

@hidegh

This comment has been minimized.

Copy link

commented Feb 17, 2017

@bjorn-ali-goransson
i still wote for nhibernate. it's more mature and had features from 2007 that EF6 still has not. not to mention that it's architecture must be cleaner, cause a 3rd party fluent mapping api to NH is more powerful that the original one in EF6.

Actually I did a test (last week) and here's the result:

EF6 - due to convention I can do a protected internal virtual property mapping (with underscore prefix) and can automate to generate clean column names - but all the way how this is solved with conventions is too primitive (not as intuitive as with fluent NH). even collection encapsulation can be solved. so almost a hit, almost, because while using IQueryable, for includes you need to use not the main property but the "backing" one - and this is not an intuitive way to work with IQueryable.

EF Core - now it seems they don't map fields by default (but need to re-verify). anyhow it seemed perfect. I got the feeling of it, started to like it despite not having real conventions, missing NxN, group by not on the database level... Then came the big suprise what the missing lazy load feature means - it means problems, real big problems that will force you into debug. It's not the lazy load what I miss, cause eager loading is a must with ORM. But when using NH/EF6 and not having DB connection and a lazy-load proxy is hit, guess what happens: right, you got an exception (so actually the missing lazy load meas missing exceptions). Now EF Core does not use proxies (nor custom nor 3rd parties). So anything not eager loaded is NULL or an empty collection. So EF Core will never complain that you are accessing something that is not loaded, it simply serves you with stupid values (nulls and empty collections). If you think this is not a big issue, ask people who had similar issue with Linq2sql and wasted productive hours to find a bug...

Despite that in 2012 the main team left NHibernate, now it's managed still, but there are less frequent releases, it's still in a beter shape than any of the existings EF's backed by Microsoft. And just to be mean a bit: a framework, not production ready (perf. issues due group by, the mentioned lazy load issue), how can have a version number above or equal 1.0?

If someone asks me, I always tell this: to NH you got a nice manual how to do things...to EF you get documents on how to hack it, so that you get a near to ORM feeling of it. Sorry guys, EF Core might once be a nice product, but in v1.1 it is still not, despite having an open sourced ORM there for more than a decade you could learn from...

@jnm2

This comment has been minimized.

Copy link

commented Feb 17, 2017

@hidegh NHibernate not having any async support is a dealbreaker for me.

@hidegh

This comment has been minimized.

Copy link

commented Feb 18, 2017

@jnm2 a clean code will keep cost of quality low (less bugs, easier to maintain, easier to extend). considering prices for a developer vs. price for better hardware... btw. if you need performance, there's nothing wrong to have NHibernate for the command and views and EF (even database-first) with async for the read model.

@jnm2

This comment has been minimized.

Copy link

commented Feb 18, 2017

I'm most familiar with writing desktop clients, and from personal experience, you need async saves. Otherwise you end up with UI lag or thread safety issues to work around. Still a dealbreaker for me.

@bjorn-ali-goransson

This comment has been minimized.

Copy link

commented Feb 18, 2017

@andez2000

This comment has been minimized.

Copy link

commented Dec 22, 2017

Any progress on using readonly fields and properties with Entity Framework. Currently I just want to pass in a Guid into the constructor which is stored in a readonly property ala #10400.

Maybe we could have a Materializer which could feed in mappings into some OnCreate where we could decide which constructor to call to materialize our entity?

class InvoiceEntityTypeConfiguration : IEntityTypeConfiguration<Invoice>
{
    public void Configure(EntityTypeBuilder<Invoice> builder)
    {
        builder.ToTable("Invoices", "dbo");
        builder.Materializer.OnCreate((PropertyMap pm) =>
        {
            return new Invoice(pm.Id, pm.Number);
        });
    }
}

Probably a dumb thought, but these kind of design decisions is killing me. I don't want to duplicate an entity into its own state type if I can help it.

ajcvickers added a commit that referenced this issue Dec 31, 2017

Initial implementation of lazy-loading and entities with constructors
Parts of issues #3342, #240, #10509, #3797

The main things here are:
- Support for injecting values into parameterized entity constructors
  - Property values are injected if the parameter type and name matches
  - The current DbContext as DbContext or a derived DbContext type
  - A service from the internal or external service provider
  - A delegate to a method of a service
- Use of the above to inject lazy loading capabilities into entities

For lazy loading, either the ILazyLoader service can be injected directly, or a delegate can be injected if the entity class cannot take a dependency on the EF assembly--see the examples below.

Currently all constructor injection is done by convention.

Remaining work includes:
- API/attributes to configure the constructor binding
- Allow factory to be used instead of using the constructor directly. (Functional already, but no API or convention to configure it.)
- Allow property injection for services
- Configuration of which entities/properties should be lazy loaded and which should not

### Examples

In this example EF will use the private constructor passing in values from the database when creating entity instances. (Note that it is assumed that _blogId has been configured as the key.)

```C#
public class Blog
{
    private int _blogId;

    // This constructor used by EF Core
    private Blog(
        int blogId,
        string title,
        int? monthlyRevenue)
    {
        _blogId = blogId;
        Title = title;
        MonthlyRevenue = monthlyRevenue;
    }

    public Blog(
        string title,
        int? monthlyRevenue = null)
        : this(0, title, monthlyRevenue)
    {
    }

    public string Title { get; }
    public int? MonthlyRevenue { get; set; }
}
```

In this example, EF will inject the ILazyLoader instance, which is then used to enable lazy-loading on navigation properties. Note that the navigation properties must have backing fields and all access by EF will go through the backing fields to prevent EF triggering lazy loading itself.

```C#
public class LazyBlog
{
    private readonly ILazyLoader _loader;
    private ICollection<LazyPost> _lazyPosts = new List<LazyPost>();

    public LazyBlog()
    {
    }

    private LazyBlog(ILazyLoader loader)
    {
        _loader = loader;
    }

    public int Id { get; set; }

    public ICollection<LazyPost> LazyPosts
        => _loader.Load(this, ref _lazyPosts);
}

public class LazyPost
{
    private readonly ILazyLoader _loader;
    private LazyBlog _lazyBlog;

    public LazyPost()
    {
    }

    private LazyPost(ILazyLoader loader)
    {
        _loader = loader;
    }

    public int Id { get; set; }

    public LazyBlog LazyBlog
    {
        get => _loader.Load(this, ref _lazyBlog);
        set => _lazyBlog = value;
    }
}
```

This example is the same as the last example, except EF is matching the delegate type and parameter name and injecting a delegate for the ILazyLoader.Load method so that the entity class does not need to reference the EF assembly. A small extension method can be included in the entity assembly to make it a bit easier to use the delegate.

```C#
public class LazyPocoBlog
{
    private readonly Action<object, string> _loader;
    private ICollection<LazyPocoPost> _lazyPocoPosts = new List<LazyPocoPost>();

    public LazyPocoBlog()
    {
    }

    private LazyPocoBlog(Action<object, string> lazyLoader)
    {
        _loader = lazyLoader;
    }

    public int Id { get; set; }

    public ICollection<LazyPocoPost> LazyPocoPosts
        => _loader.Load(this, ref _lazyPocoPosts);
}

public class LazyPocoPost
{
    private readonly Action<object, string> _loader;
    private LazyPocoBlog _lazyPocoBlog;

    public LazyPocoPost()
    {
    }

    private LazyPocoPost(Action<object, string> lazyLoader)
    {
        _loader = lazyLoader;
    }

    public int Id { get; set; }

    public LazyPocoBlog LazyPocoBlog
    {
        get => _loader.Load(this, ref _lazyPocoBlog);
        set => _lazyPocoBlog = value;
    }
}

public static class TestPocoLoadingExtensions
{
    public static TRelated Load<TRelated>(
        this Action<object, string> loader,
        object entity,
        ref TRelated navigationField,
        [CallerMemberName] string navigationName = null)
        where TRelated : class
    {
        loader?.Invoke(entity, navigationName);

        return navigationField;
    }
}
```

ajcvickers added a commit that referenced this issue Jan 1, 2018

Initial implementation of lazy-loading and entities with constructors
Parts of issues #3342, #240, #10509, #3797

The main things here are:
- Support for injecting values into parameterized entity constructors
  - Property values are injected if the parameter type and name matches
  - The current DbContext as DbContext or a derived DbContext type
  - A service from the internal or external service provider
  - A delegate to a method of a service
- Use of the above to inject lazy loading capabilities into entities

For lazy loading, either the ILazyLoader service can be injected directly, or a delegate can be injected if the entity class cannot take a dependency on the EF assembly--see the examples below.

Currently all constructor injection is done by convention.

Remaining work includes:
- API/attributes to configure the constructor binding
- Allow factory to be used instead of using the constructor directly. (Functional already, but no API or convention to configure it.)
- Allow property injection for services
- Configuration of which entities/properties should be lazy loaded and which should not

### Examples

In this example EF will use the private constructor passing in values from the database when creating entity instances. (Note that it is assumed that _blogId has been configured as the key.)

```C#
public class Blog
{
    private int _blogId;

    // This constructor used by EF Core
    private Blog(
        int blogId,
        string title,
        int? monthlyRevenue)
    {
        _blogId = blogId;
        Title = title;
        MonthlyRevenue = monthlyRevenue;
    }

    public Blog(
        string title,
        int? monthlyRevenue = null)
        : this(0, title, monthlyRevenue)
    {
    }

    public string Title { get; }
    public int? MonthlyRevenue { get; set; }
}
```

In this example, EF will inject the ILazyLoader instance, which is then used to enable lazy-loading on navigation properties. Note that the navigation properties must have backing fields and all access by EF will go through the backing fields to prevent EF triggering lazy loading itself.

```C#
public class LazyBlog
{
    private readonly ILazyLoader _loader;
    private ICollection<LazyPost> _lazyPosts = new List<LazyPost>();

    public LazyBlog()
    {
    }

    private LazyBlog(ILazyLoader loader)
    {
        _loader = loader;
    }

    public int Id { get; set; }

    public ICollection<LazyPost> LazyPosts
        => _loader.Load(this, ref _lazyPosts);
}

public class LazyPost
{
    private readonly ILazyLoader _loader;
    private LazyBlog _lazyBlog;

    public LazyPost()
    {
    }

    private LazyPost(ILazyLoader loader)
    {
        _loader = loader;
    }

    public int Id { get; set; }

    public LazyBlog LazyBlog
    {
        get => _loader.Load(this, ref _lazyBlog);
        set => _lazyBlog = value;
    }
}
```

This example is the same as the last example, except EF is matching the delegate type and parameter name and injecting a delegate for the ILazyLoader.Load method so that the entity class does not need to reference the EF assembly. A small extension method can be included in the entity assembly to make it a bit easier to use the delegate.

```C#
public class LazyPocoBlog
{
    private readonly Action<object, string> _loader;
    private ICollection<LazyPocoPost> _lazyPocoPosts = new List<LazyPocoPost>();

    public LazyPocoBlog()
    {
    }

    private LazyPocoBlog(Action<object, string> lazyLoader)
    {
        _loader = lazyLoader;
    }

    public int Id { get; set; }

    public ICollection<LazyPocoPost> LazyPocoPosts
        => _loader.Load(this, ref _lazyPocoPosts);
}

public class LazyPocoPost
{
    private readonly Action<object, string> _loader;
    private LazyPocoBlog _lazyPocoBlog;

    public LazyPocoPost()
    {
    }

    private LazyPocoPost(Action<object, string> lazyLoader)
    {
        _loader = lazyLoader;
    }

    public int Id { get; set; }

    public LazyPocoBlog LazyPocoBlog
    {
        get => _loader.Load(this, ref _lazyPocoBlog);
        set => _lazyPocoBlog = value;
    }
}

public static class TestPocoLoadingExtensions
{
    public static TRelated Load<TRelated>(
        this Action<object, string> loader,
        object entity,
        ref TRelated navigationField,
        [CallerMemberName] string navigationName = null)
        where TRelated : class
    {
        loader?.Invoke(entity, navigationName);

        return navigationField;
    }
}
```

ajcvickers added a commit that referenced this issue Jan 2, 2018

Initial implementation of lazy-loading and entities with constructors
Parts of issues #3342, #240, #10509, #3797

The main things here are:
- Support for injecting values into parameterized entity constructors
  - Property values are injected if the parameter type and name matches
  - The current DbContext as DbContext or a derived DbContext type
  - A service from the internal or external service provider
  - A delegate to a method of a service
  - The IEntityType for the entity
- Use of the above to inject lazy loading capabilities into entities

For lazy loading, either the ILazyLoader service can be injected directly, or a delegate can be injected if the entity class cannot take a dependency on the EF assembly--see the examples below.

Currently all constructor injection is done by convention.

Remaining work includes:
- API/attributes to configure the constructor binding
- Allow factory to be used instead of using the constructor directly. (Functional already, but no API or convention to configure it.)
- Allow property injection for services
- Configuration of which entities/properties should be lazy loaded and which should not

### Examples

In this example EF will use the private constructor passing in values from the database when creating entity instances. (Note that it is assumed that _blogId has been configured as the key.)

```C#
public class Blog
{
    private int _blogId;

    // This constructor used by EF Core
    private Blog(
        int blogId,
        string title,
        int? monthlyRevenue)
    {
        _blogId = blogId;
        Title = title;
        MonthlyRevenue = monthlyRevenue;
    }

    public Blog(
        string title,
        int? monthlyRevenue = null)
        : this(0, title, monthlyRevenue)
    {
    }

    public string Title { get; }
    public int? MonthlyRevenue { get; set; }
}
```

In this example, EF will inject the ILazyLoader instance, which is then used to enable lazy-loading on navigation properties. Note that the navigation properties must have backing fields and all access by EF will go through the backing fields to prevent EF triggering lazy loading itself.

```C#
public class LazyBlog
{
    private readonly ILazyLoader _loader;
    private ICollection<LazyPost> _lazyPosts = new List<LazyPost>();

    public LazyBlog()
    {
    }

    private LazyBlog(ILazyLoader loader)
    {
        _loader = loader;
    }

    public int Id { get; set; }

    public ICollection<LazyPost> LazyPosts
        => _loader.Load(this, ref _lazyPosts);
}

public class LazyPost
{
    private readonly ILazyLoader _loader;
    private LazyBlog _lazyBlog;

    public LazyPost()
    {
    }

    private LazyPost(ILazyLoader loader)
    {
        _loader = loader;
    }

    public int Id { get; set; }

    public LazyBlog LazyBlog
    {
        get => _loader.Load(this, ref _lazyBlog);
        set => _lazyBlog = value;
    }
}
```

This example is the same as the last example, except EF is matching the delegate type and parameter name and injecting a delegate for the ILazyLoader.Load method so that the entity class does not need to reference the EF assembly. A small extension method can be included in the entity assembly to make it a bit easier to use the delegate.

```C#
public class LazyPocoBlog
{
    private readonly Action<object, string> _loader;
    private ICollection<LazyPocoPost> _lazyPocoPosts = new List<LazyPocoPost>();

    public LazyPocoBlog()
    {
    }

    private LazyPocoBlog(Action<object, string> lazyLoader)
    {
        _loader = lazyLoader;
    }

    public int Id { get; set; }

    public ICollection<LazyPocoPost> LazyPocoPosts
        => _loader.Load(this, ref _lazyPocoPosts);
}

public class LazyPocoPost
{
    private readonly Action<object, string> _loader;
    private LazyPocoBlog _lazyPocoBlog;

    public LazyPocoPost()
    {
    }

    private LazyPocoPost(Action<object, string> lazyLoader)
    {
        _loader = lazyLoader;
    }

    public int Id { get; set; }

    public LazyPocoBlog LazyPocoBlog
    {
        get => _loader.Load(this, ref _lazyPocoBlog);
        set => _lazyPocoBlog = value;
    }
}

public static class TestPocoLoadingExtensions
{
    public static TRelated Load<TRelated>(
        this Action<object, string> loader,
        object entity,
        ref TRelated navigationField,
        [CallerMemberName] string navigationName = null)
        where TRelated : class
    {
        loader?.Invoke(entity, navigationName);

        return navigationField;
    }
}
```

ajcvickers added a commit that referenced this issue Jan 2, 2018

Initial implementation of lazy-loading and entities with constructors
Parts of issues #3342, #240, #10509, #3797

The main things here are:
- Support for injecting values into parameterized entity constructors
  - Property values are injected if the parameter type and name matches
  - The current DbContext as DbContext or a derived DbContext type
  - A service from the internal or external service provider
  - A delegate to a method of a service
  - The IEntityType for the entity
- Use of the above to inject lazy loading capabilities into entities

For lazy loading, either the ILazyLoader service can be injected directly, or a delegate can be injected if the entity class cannot take a dependency on the EF assembly--see the examples below.

Currently all constructor injection is done by convention.

Remaining work includes:
- API/attributes to configure the constructor binding
- Allow factory to be used instead of using the constructor directly. (Functional already, but no API or convention to configure it.)
- Allow property injection for services
- Configuration of which entities/properties should be lazy loaded and which should not

### Examples

In this example EF will use the private constructor passing in values from the database when creating entity instances. (Note that it is assumed that _blogId has been configured as the key.)

```C#
public class Blog
{
    private int _blogId;

    // This constructor used by EF Core
    private Blog(
        int blogId,
        string title,
        int? monthlyRevenue)
    {
        _blogId = blogId;
        Title = title;
        MonthlyRevenue = monthlyRevenue;
    }

    public Blog(
        string title,
        int? monthlyRevenue = null)
        : this(0, title, monthlyRevenue)
    {
    }

    public string Title { get; }
    public int? MonthlyRevenue { get; set; }
}
```

In this example, EF will inject the ILazyLoader instance, which is then used to enable lazy-loading on navigation properties. Note that the navigation properties must have backing fields and all access by EF will go through the backing fields to prevent EF triggering lazy loading itself.

```C#
public class LazyBlog
{
    private readonly ILazyLoader _loader;
    private ICollection<LazyPost> _lazyPosts = new List<LazyPost>();

    public LazyBlog()
    {
    }

    private LazyBlog(ILazyLoader loader)
    {
        _loader = loader;
    }

    public int Id { get; set; }

    public ICollection<LazyPost> LazyPosts
        => _loader.Load(this, ref _lazyPosts);
}

public class LazyPost
{
    private readonly ILazyLoader _loader;
    private LazyBlog _lazyBlog;

    public LazyPost()
    {
    }

    private LazyPost(ILazyLoader loader)
    {
        _loader = loader;
    }

    public int Id { get; set; }

    public LazyBlog LazyBlog
    {
        get => _loader.Load(this, ref _lazyBlog);
        set => _lazyBlog = value;
    }
}
```

This example is the same as the last example, except EF is matching the delegate type and parameter name and injecting a delegate for the ILazyLoader.Load method so that the entity class does not need to reference the EF assembly. A small extension method can be included in the entity assembly to make it a bit easier to use the delegate.

```C#
public class LazyPocoBlog
{
    private readonly Action<object, string> _loader;
    private ICollection<LazyPocoPost> _lazyPocoPosts = new List<LazyPocoPost>();

    public LazyPocoBlog()
    {
    }

    private LazyPocoBlog(Action<object, string> lazyLoader)
    {
        _loader = lazyLoader;
    }

    public int Id { get; set; }

    public ICollection<LazyPocoPost> LazyPocoPosts
        => _loader.Load(this, ref _lazyPocoPosts);
}

public class LazyPocoPost
{
    private readonly Action<object, string> _loader;
    private LazyPocoBlog _lazyPocoBlog;

    public LazyPocoPost()
    {
    }

    private LazyPocoPost(Action<object, string> lazyLoader)
    {
        _loader = lazyLoader;
    }

    public int Id { get; set; }

    public LazyPocoBlog LazyPocoBlog
    {
        get => _loader.Load(this, ref _lazyPocoBlog);
        set => _lazyPocoBlog = value;
    }
}

public static class TestPocoLoadingExtensions
{
    public static TRelated Load<TRelated>(
        this Action<object, string> loader,
        object entity,
        ref TRelated navigationField,
        [CallerMemberName] string navigationName = null)
        where TRelated : class
    {
        loader?.Invoke(entity, navigationName);

        return navigationField;
    }
}
```

ajcvickers added a commit that referenced this issue Jan 3, 2018

Initial implementation of lazy-loading and entities with constructors
Parts of issues #3342, #240, #10509, #3797

The main things here are:
- Support for injecting values into parameterized entity constructors
  - Property values are injected if the parameter type and name matches
  - The current DbContext as DbContext or a derived DbContext type
  - A service from the internal or external service provider
  - A delegate to a method of a service
  - The IEntityType for the entity
- Use of the above to inject lazy loading capabilities into entities

For lazy loading, either the ILazyLoader service can be injected directly, or a delegate can be injected if the entity class cannot take a dependency on the EF assembly--see the examples below.

Currently all constructor injection is done by convention.

Remaining work includes:
- API/attributes to configure the constructor binding
- Allow factory to be used instead of using the constructor directly. (Functional already, but no API or convention to configure it.)
- Allow property injection for services
- Configuration of which entities/properties should be lazy loaded and which should not

### Examples

In this example EF will use the private constructor passing in values from the database when creating entity instances. (Note that it is assumed that _blogId has been configured as the key.)

```C#
public class Blog
{
    private int _blogId;

    // This constructor used by EF Core
    private Blog(
        int blogId,
        string title,
        int? monthlyRevenue)
    {
        _blogId = blogId;
        Title = title;
        MonthlyRevenue = monthlyRevenue;
    }

    public Blog(
        string title,
        int? monthlyRevenue = null)
        : this(0, title, monthlyRevenue)
    {
    }

    public string Title { get; }
    public int? MonthlyRevenue { get; set; }
}
```

In this example, EF will inject the ILazyLoader instance, which is then used to enable lazy-loading on navigation properties. Note that the navigation properties must have backing fields and all access by EF will go through the backing fields to prevent EF triggering lazy loading itself.

```C#
public class LazyBlog
{
    private readonly ILazyLoader _loader;
    private ICollection<LazyPost> _lazyPosts = new List<LazyPost>();

    public LazyBlog()
    {
    }

    private LazyBlog(ILazyLoader loader)
    {
        _loader = loader;
    }

    public int Id { get; set; }

    public ICollection<LazyPost> LazyPosts
        => _loader.Load(this, ref _lazyPosts);
}

public class LazyPost
{
    private readonly ILazyLoader _loader;
    private LazyBlog _lazyBlog;

    public LazyPost()
    {
    }

    private LazyPost(ILazyLoader loader)
    {
        _loader = loader;
    }

    public int Id { get; set; }

    public LazyBlog LazyBlog
    {
        get => _loader.Load(this, ref _lazyBlog);
        set => _lazyBlog = value;
    }
}
```

This example is the same as the last example, except EF is matching the delegate type and parameter name and injecting a delegate for the ILazyLoader.Load method so that the entity class does not need to reference the EF assembly. A small extension method can be included in the entity assembly to make it a bit easier to use the delegate.

```C#
public class LazyPocoBlog
{
    private readonly Action<object, string> _loader;
    private ICollection<LazyPocoPost> _lazyPocoPosts = new List<LazyPocoPost>();

    public LazyPocoBlog()
    {
    }

    private LazyPocoBlog(Action<object, string> lazyLoader)
    {
        _loader = lazyLoader;
    }

    public int Id { get; set; }

    public ICollection<LazyPocoPost> LazyPocoPosts
        => _loader.Load(this, ref _lazyPocoPosts);
}

public class LazyPocoPost
{
    private readonly Action<object, string> _loader;
    private LazyPocoBlog _lazyPocoBlog;

    public LazyPocoPost()
    {
    }

    private LazyPocoPost(Action<object, string> lazyLoader)
    {
        _loader = lazyLoader;
    }

    public int Id { get; set; }

    public LazyPocoBlog LazyPocoBlog
    {
        get => _loader.Load(this, ref _lazyPocoBlog);
        set => _lazyPocoBlog = value;
    }
}

public static class TestPocoLoadingExtensions
{
    public static TRelated Load<TRelated>(
        this Action<object, string> loader,
        object entity,
        ref TRelated navigationField,
        [CallerMemberName] string navigationName = null)
        where TRelated : class
    {
        loader?.Invoke(entity, navigationName);

        return navigationField;
    }
}
```

ajcvickers added a commit that referenced this issue Jan 3, 2018

Initial implementation of lazy-loading and entities with constructors
Parts of issues #3342, #240, #10509, #3797

The main things here are:
- Support for injecting values into parameterized entity constructors
  - Property values are injected if the parameter type and name matches
  - The current DbContext as DbContext or a derived DbContext type
  - A service from the internal or external service provider
  - A delegate to a method of a service
  - The IEntityType for the entity
- Use of the above to inject lazy loading capabilities into entities

For lazy loading, either the ILazyLoader service can be injected directly, or a delegate can be injected if the entity class cannot take a dependency on the EF assembly--see the examples below.

Currently all constructor injection is done by convention.

Remaining work includes:
- API/attributes to configure the constructor binding
- Allow factory to be used instead of using the constructor directly. (Functional already, but no API or convention to configure it.)
- Allow property injection for services
- Configuration of which entities/properties should be lazy loaded and which should not

### Examples

In this example EF will use the private constructor passing in values from the database when creating entity instances. (Note that it is assumed that _blogId has been configured as the key.)

```C#
public class Blog
{
    private int _blogId;

    // This constructor used by EF Core
    private Blog(
        int blogId,
        string title,
        int? monthlyRevenue)
    {
        _blogId = blogId;
        Title = title;
        MonthlyRevenue = monthlyRevenue;
    }

    public Blog(
        string title,
        int? monthlyRevenue = null)
        : this(0, title, monthlyRevenue)
    {
    }

    public string Title { get; }
    public int? MonthlyRevenue { get; set; }
}
```

In this example, EF will inject the ILazyLoader instance, which is then used to enable lazy-loading on navigation properties. Note that the navigation properties must have backing fields and all access by EF will go through the backing fields to prevent EF triggering lazy loading itself.

```C#
public class LazyBlog
{
    private readonly ILazyLoader _loader;
    private ICollection<LazyPost> _lazyPosts = new List<LazyPost>();

    public LazyBlog()
    {
    }

    private LazyBlog(ILazyLoader loader)
    {
        _loader = loader;
    }

    public int Id { get; set; }

    public ICollection<LazyPost> LazyPosts
        => _loader.Load(this, ref _lazyPosts);
}

public class LazyPost
{
    private readonly ILazyLoader _loader;
    private LazyBlog _lazyBlog;

    public LazyPost()
    {
    }

    private LazyPost(ILazyLoader loader)
    {
        _loader = loader;
    }

    public int Id { get; set; }

    public LazyBlog LazyBlog
    {
        get => _loader.Load(this, ref _lazyBlog);
        set => _lazyBlog = value;
    }
}
```

This example is the same as the last example, except EF is matching the delegate type and parameter name and injecting a delegate for the ILazyLoader.Load method so that the entity class does not need to reference the EF assembly. A small extension method can be included in the entity assembly to make it a bit easier to use the delegate.

```C#
public class LazyPocoBlog
{
    private readonly Action<object, string> _loader;
    private ICollection<LazyPocoPost> _lazyPocoPosts = new List<LazyPocoPost>();

    public LazyPocoBlog()
    {
    }

    private LazyPocoBlog(Action<object, string> lazyLoader)
    {
        _loader = lazyLoader;
    }

    public int Id { get; set; }

    public ICollection<LazyPocoPost> LazyPocoPosts
        => _loader.Load(this, ref _lazyPocoPosts);
}

public class LazyPocoPost
{
    private readonly Action<object, string> _loader;
    private LazyPocoBlog _lazyPocoBlog;

    public LazyPocoPost()
    {
    }

    private LazyPocoPost(Action<object, string> lazyLoader)
    {
        _loader = lazyLoader;
    }

    public int Id { get; set; }

    public LazyPocoBlog LazyPocoBlog
    {
        get => _loader.Load(this, ref _lazyPocoBlog);
        set => _lazyPocoBlog = value;
    }
}

public static class TestPocoLoadingExtensions
{
    public static TRelated Load<TRelated>(
        this Action<object, string> loader,
        object entity,
        ref TRelated navigationField,
        [CallerMemberName] string navigationName = null)
        where TRelated : class
    {
        loader?.Invoke(entity, navigationName);

        return navigationField;
    }
}
```
@smarts

This comment has been minimized.

Copy link

commented Mar 8, 2019

Which one of the issues in this issue's description handles support for unsigned integer types?

@ajcvickers

This comment has been minimized.

Copy link
Member

commented Mar 8, 2019

@smarts Unsigned integer types are supported as of EF Core 2.1.

@smarts

This comment has been minimized.

Copy link

commented Mar 10, 2019

@ajcvickers is there documentation on how to use this feature? My team recently tried to use them and was getting an exception that EF Core was expecting Int32 instead of UInt32. Do you know if this feature works for shadow properties too? Also, does it work for any DB integer type?
Our specific use-cases are SQL Server and PostgreSQL

@ajcvickers

This comment has been minimized.

Copy link
Member

commented Mar 11, 2019

@smarts Some databases natively support storing unsigned types, in which case they should behave like any other property. Some databases, like SQL Server, don't supported unsigned types, but EF should automatically perform a value conversion if you have such a type in your model. In this case the value is stored in the database as a bigger signed type, by default.

If you have an entity type with unsigned properties and you are seeing errors, then please file a new issue with a small, runnable project/solution of complete code listing that demonstrates the behavior you are seeing.

@smarts

This comment has been minimized.

Copy link

commented Mar 28, 2019

@ajcvickers sorry for the delay in responding.

In this case the value is stored in the database as a bigger signed type, by default.

Does that mean that using UInt32 for an entity's property will only work if the DB type for the corresponding column supports the full range of values for UInt32? I.e., we want to use INT in the DB and UInt32 on the entity.
While I understand that this conversion is generally bad, in the specific case of [generated] IDs the risk becomes negligible. Admittedly, this is based on my minimal experience with SQL DBs, so I apologize if this assumption is incorrect.
Is it possible to allow this generally-risky conversion for the negligible-risk scenario of IDs via some custom EF Convention?

@roji

This comment has been minimized.

Copy link
Member

commented Mar 29, 2019

@smarts take a look at value converters - you can set up lossy value conversions, but EF will not that automatically for you (as it does with uint->long). However, lossy value conversions may get a bit tricky in some scenarios, and are best avoided if you have an easy lossless alternative (such as long).

@markusschaber

This comment has been minimized.

Copy link

commented Mar 29, 2019

@roji As far as I can see, conversion between UInt32 and Int32 is lossless if you use "C-Style" casts. Negative int32 values are mapped to "high" unsigned values and back, one just should be careful when interpreting the converted values (e. G. ordering is different).
SQL Server, PostgreSQL and MySQL provide "int" as a 32 bit signed data type, so trying to store an uint32 there via C-Style cast looks feasible. (I didn't check other DBs).

@roji

This comment has been minimized.

Copy link
Member

commented Mar 29, 2019

@markusschaber you're technically right: if your value converter does a simple cast in C#, then uint values which are larger than int.MaxValue will show up as negatives. While this is technically lossless and would work, as you say there are some tricky caveats.

Given that databases typically have a larger bigint type which can contain all uint values, it generally really makes sense to convert to that - trying to save 4 bytes isn't going to be justifiable in most cases.

@smarts

This comment has been minimized.

Copy link

commented Mar 29, 2019

@markusschaber i'm not sure if you noticed, but i'm specifically talking about auto-generated IDs in the database. Using long for the entity's property type has the same problem (allowing negative values), which isn't what I want. Also, as far as large values… since the value only ever comes from the DB (i.e., not set by any C# code outside of EF Core reading DB values), it will never be larger than Int32.MaxValue (because INT is the DB type).

@ajcvickers

This comment has been minimized.

Copy link
Member

commented Mar 29, 2019

Just to close the loop on this. When deciding how to convert automatically, we look at:

  • Is is lossy?
  • Does it preserve semantics on the database--most notably ordering?

The second point is why we convert uint to long, and not uint to int, since for the latter, if the uint does have a value that can't "fit" it will still be stored, but it will be interpreted as negative by the database and hence will have a different ordering.

That being said, it's safe to convert uint to int if you know that the domain space for your uints can fit in an int, or if a specific ordering on the database is not needed. One way to do it is:

modelBuilder.Entity<Foo>().Property(e => e.MyUInt).HasConversion<int>();

Finally, there are currently some limitations for conversion on keys: see #11597

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.