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

Open
rowanmiller opened this Issue May 22, 2014 · 25 comments

Comments

Projects
None yet
@rowanmiller
Member

rowanmiller 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
  • #752 Custom collection patterns
  • #2919 Richer collection support
  • #2968 Custom property access patterns
  • #242 Simple data store to CLR conversions (custom type mapping)
  • #3342 Flexible construction through factory methods and DI
  • #4179 Collections of scalar types
@rowanmiller

This comment has been minimized.

Show comment
Hide comment
@rowanmiller

rowanmiller Nov 10, 2014

Member

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

Member

rowanmiller 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.

Show comment
Hide comment
@divega

divega Sep 1, 2015

Member

@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?

Member

divega 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.

Show comment
Hide comment
@rowanmiller

rowanmiller Sep 1, 2015

Member

@divega yep that works

Member

rowanmiller commented Sep 1, 2015

@divega yep that works

@divega

This comment has been minimized.

Show comment
Hide comment
@divega

divega Sep 1, 2015

Member

Otherwise I can merge it all here...

Member

divega commented Sep 1, 2015

Otherwise I can merge it all here...

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

@divega

This comment has been minimized.

Show comment
Hide comment
@divega

divega Sep 1, 2015

Member

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

Member

divega 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.

Show comment
Hide comment
@biqas

biqas 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.

biqas 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.

Show comment
Hide comment
@bjorn-ali-goransson

bjorn-ali-goransson 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 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.

Show comment
Hide comment
@bjorn-ali-goransson

bjorn-ali-goransson 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.

@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.

@Ma3yTa

This comment has been minimized.

Show comment
Hide comment
@Ma3yTa

Ma3yTa Sep 29, 2016

@rowanmiller what is blocking to implement this. Maybe, I can help with this?

Ma3yTa commented Sep 29, 2016

@rowanmiller what is blocking to implement this. Maybe, I can help with this?

@divega divega referenced this issue in aspnet/EntityFramework6 Sep 29, 2016

Closed

Complex Type Serialization via FluentAPI #72

@Antaris

This comment has been minimized.

Show comment
Hide comment
@Antaris

Antaris Oct 10, 2016

My example of where this would be useful would be something like the following:

public class BlogPost
{
    // Other properties omitted for brevity...

    public TagSet Tags { get; set; }
}

In my database, I want to store tags as a flat list (not a relational table), purely for lookup efficiency. It's simple to test like WHERE [Tags] LIKE '%|MyTag|%' to return values, etc.

With this in mind, I *could * form a type, like the following:

public struct TagSet : ICollection<string>
{
    private string _tagString;
    private Lazy<HashSet<string>> _collectionThunk;
    private bool _modified;
    private bool _hasValue;

    public TagSet(string tagString)
    {
        _tagString = tagString;
        _collectionThunk = new Lazy<HashSet<string>>(() => ResolveCollection(tagString));
        _modified = false;
        _hasValue = true;
    }

    public int Count => _collectionThunk.Value.Count;

    public bool IsReadOnly => false;

    public void Add(string item)
    {
        _collectionThunk.Value.Add(item);
        _modified = true;
    }

    public void Clear()
    {
        _tagString = null;
        _collectionThunk = new Lazy<HashSet<string>>(() => ResolveCollection(null));
        _modified = true;
    }

    public bool Contains(string item)
    {
        if (string.IsNullOrEmpty(_tagString))
        {
            return false;
        }

        if (_collectionThunk.IsValueCreated)
        {
            return _collectionThunk.Value.Contains(item);
        }

        var culture = CultureInfo.CurrentCulture;
        return culture.CompareInfo.IndexOf(_tagString, $"|{item}|", CompareOptions.IgnoreCase) >= 0;
    }

    public void CopyTo(string[] array, int arrayIndex) => _collectionThunk.Value.CopyTo(array, arrayIndex);

    public IEnumerator<string> GetEnumerator() => _collectionThunk.Value.GetEnumerator();

    public bool Remove(string item)
    {
        if (!string.IsNullOrEmpty(_tagString))
        {
            _modified = true;
            return _collectionThunk.Value.Remove(item);
        }
        return false;
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    private static HashSet<string> ResolveCollection(string value)
    {
        var hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

        if (!string.IsNullOrEmpty(value))
        {
            value = value.Trim('|');
            foreach (string tag in value.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries))
            {
                hashSet.Add(tag);
            }
        }

        return hashSet;
    }

    public static implicit operator string(TagSet tags)
    {
        if (tags._modified)
        {
            if (tags._collectionThunk.IsValueCreated)
            {
                if (tags._collectionThunk.Value.Count == 0)
                {
                    return null;
                }

                return $"|{string.Join("||", tags._collectionThunk.Value)}|";
            }
        }

        return tags._tagString;
    }

    public static implicit operator TagSet(string tagString) => new TagSet(tagString);

    public override string ToString() => this;

    public bool HasValue => _hasValue;
}

Which accepts a string, such like |Hello||World||These||Are My||Tags|, and allows lazy evaluation of those as an ICollection<string. It can react to changes and allow me to rebuild my flat tag string when I need it to.

I wondered how I would like this to integrate into EF, and perhaps a custom interface (taking some inspiration from AutoMapper):

public interface IValueMapper<TSource, TTarget>
{
    TTarget Map(TSource source);

    TSource Map(TTarget target);
}

public class TagSetValueMapper : IValueMapper<string, TagSet>
{
    public string Map(TagSet target) => target;

    public TagSet Map(string source) => new TagSet(source);
}

Perhaps we could then configure this as part of the EF model:

builder.Property(m => m.Tags).IsOptional().Maps<TagSet, string>(new TagSetValueMapper());

Honestly no ideas about how you would handle LINQ expressions though.

Thoughts? Would this be achievable? Perhaps even using a custom type converter instead? Who would be responsbile for handling DbNull checking though? EF or a mapper?

Antaris commented Oct 10, 2016

My example of where this would be useful would be something like the following:

public class BlogPost
{
    // Other properties omitted for brevity...

    public TagSet Tags { get; set; }
}

In my database, I want to store tags as a flat list (not a relational table), purely for lookup efficiency. It's simple to test like WHERE [Tags] LIKE '%|MyTag|%' to return values, etc.

With this in mind, I *could * form a type, like the following:

public struct TagSet : ICollection<string>
{
    private string _tagString;
    private Lazy<HashSet<string>> _collectionThunk;
    private bool _modified;
    private bool _hasValue;

    public TagSet(string tagString)
    {
        _tagString = tagString;
        _collectionThunk = new Lazy<HashSet<string>>(() => ResolveCollection(tagString));
        _modified = false;
        _hasValue = true;
    }

    public int Count => _collectionThunk.Value.Count;

    public bool IsReadOnly => false;

    public void Add(string item)
    {
        _collectionThunk.Value.Add(item);
        _modified = true;
    }

    public void Clear()
    {
        _tagString = null;
        _collectionThunk = new Lazy<HashSet<string>>(() => ResolveCollection(null));
        _modified = true;
    }

    public bool Contains(string item)
    {
        if (string.IsNullOrEmpty(_tagString))
        {
            return false;
        }

        if (_collectionThunk.IsValueCreated)
        {
            return _collectionThunk.Value.Contains(item);
        }

        var culture = CultureInfo.CurrentCulture;
        return culture.CompareInfo.IndexOf(_tagString, $"|{item}|", CompareOptions.IgnoreCase) >= 0;
    }

    public void CopyTo(string[] array, int arrayIndex) => _collectionThunk.Value.CopyTo(array, arrayIndex);

    public IEnumerator<string> GetEnumerator() => _collectionThunk.Value.GetEnumerator();

    public bool Remove(string item)
    {
        if (!string.IsNullOrEmpty(_tagString))
        {
            _modified = true;
            return _collectionThunk.Value.Remove(item);
        }
        return false;
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    private static HashSet<string> ResolveCollection(string value)
    {
        var hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

        if (!string.IsNullOrEmpty(value))
        {
            value = value.Trim('|');
            foreach (string tag in value.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries))
            {
                hashSet.Add(tag);
            }
        }

        return hashSet;
    }

    public static implicit operator string(TagSet tags)
    {
        if (tags._modified)
        {
            if (tags._collectionThunk.IsValueCreated)
            {
                if (tags._collectionThunk.Value.Count == 0)
                {
                    return null;
                }

                return $"|{string.Join("||", tags._collectionThunk.Value)}|";
            }
        }

        return tags._tagString;
    }

    public static implicit operator TagSet(string tagString) => new TagSet(tagString);

    public override string ToString() => this;

    public bool HasValue => _hasValue;
}

Which accepts a string, such like |Hello||World||These||Are My||Tags|, and allows lazy evaluation of those as an ICollection<string. It can react to changes and allow me to rebuild my flat tag string when I need it to.

I wondered how I would like this to integrate into EF, and perhaps a custom interface (taking some inspiration from AutoMapper):

public interface IValueMapper<TSource, TTarget>
{
    TTarget Map(TSource source);

    TSource Map(TTarget target);
}

public class TagSetValueMapper : IValueMapper<string, TagSet>
{
    public string Map(TagSet target) => target;

    public TagSet Map(string source) => new TagSet(source);
}

Perhaps we could then configure this as part of the EF model:

builder.Property(m => m.Tags).IsOptional().Maps<TagSet, string>(new TagSetValueMapper());

Honestly no ideas about how you would handle LINQ expressions though.

Thoughts? Would this be achievable? Perhaps even using a custom type converter instead? Who would be responsbile for handling DbNull checking though? EF or a mapper?

@divega

This comment has been minimized.

Show comment
Hide comment
@divega

divega Oct 10, 2016

Member

@Antaris not disagreeing that yours is a valid scenario for this feature but could you elaborate more on the motivation? You said it is "purely for lookup efficiency" but my understanding is that this hinders searchability to make persistence and retrieval potentially more efficient, e.g. with a regular index it is not possible to resolve WHERE [Tags] LIKE '%|MyTag|%', but once you apply this pattern it is no longer necessary to update and query an additional table (or tables) for the tags.

Re LINQ, as you mentioned, it becomes more complicated. Two possible general approaches:

  1. Stake in the ground: When you do something like this you are effectively opting out of searchability, so evaluating a predicate that refers to Tags on the client (or failing if client evaluation is disabled) may be good enough. It will be up to you to make sure that your queries contain predicates on criteria other than Tags so you will never load the whole table into memory.
  2. The mapping transformation (or a version of it) could be represented in a LINQ expression rather than regular code. References to Tags in a query could then be expanded by inlining that expression before we attempt to compute the SQL translation.
Member

divega commented Oct 10, 2016

@Antaris not disagreeing that yours is a valid scenario for this feature but could you elaborate more on the motivation? You said it is "purely for lookup efficiency" but my understanding is that this hinders searchability to make persistence and retrieval potentially more efficient, e.g. with a regular index it is not possible to resolve WHERE [Tags] LIKE '%|MyTag|%', but once you apply this pattern it is no longer necessary to update and query an additional table (or tables) for the tags.

Re LINQ, as you mentioned, it becomes more complicated. Two possible general approaches:

  1. Stake in the ground: When you do something like this you are effectively opting out of searchability, so evaluating a predicate that refers to Tags on the client (or failing if client evaluation is disabled) may be good enough. It will be up to you to make sure that your queries contain predicates on criteria other than Tags so you will never load the whole table into memory.
  2. The mapping transformation (or a version of it) could be represented in a LINQ expression rather than regular code. References to Tags in a query could then be expanded by inlining that expression before we attempt to compute the SQL translation.
@Antaris

This comment has been minimized.

Show comment
Hide comment
@Antaris

Antaris Oct 10, 2016

@divega

Hmmm, I hadn't thought of that :-/ very good point on the indexes front. From previous experience I've found the flat list vs many-2-many tables setup to be more efficient in terms of manageability, hadn't really considered T-SQL index performance. I'll have to think about it and see about potential impacts. I guess I'd need to weigh up searchability vs storage efficiency :-/

Unless of course, I merge the functionality for having tags, in both a flat list -> TagSet mapping, AND many-to-many relationship tables, the former for efficient reading, and the latter for searching. GIven the size of the data sets i might be working with, it might be an option. I'd have to manage the mapping tables when saving the parent entitities :-/

Antaris commented Oct 10, 2016

@divega

Hmmm, I hadn't thought of that :-/ very good point on the indexes front. From previous experience I've found the flat list vs many-2-many tables setup to be more efficient in terms of manageability, hadn't really considered T-SQL index performance. I'll have to think about it and see about potential impacts. I guess I'd need to weigh up searchability vs storage efficiency :-/

Unless of course, I merge the functionality for having tags, in both a flat list -> TagSet mapping, AND many-to-many relationship tables, the former for efficient reading, and the latter for searching. GIven the size of the data sets i might be working with, it might be an option. I'd have to manage the mapping tables when saving the parent entitities :-/

@Antaris

This comment has been minimized.

Show comment
Hide comment
@Antaris

Antaris Oct 11, 2016

@divega Out of interest I did some proof of concept work by fiddling with some of the internal API, and I got it working for the materialization point of view.

https://gist.github.com/Antaris/ca0f271311fdb84ef3a8db95b149a307

But, because my struct isn't immutable, the state manager doesn't pick up the change.

After playing around with it, I hacked in an ISnapshotable<T> interface which essentially cloned the value for the snapshot and then it works, but couldn't do that through a replaceable EF service (had to mod SnapshotFactoryFactory but as a proof of concept - quite happy with it.

Next stop - query expressions!

Antaris commented Oct 11, 2016

@divega Out of interest I did some proof of concept work by fiddling with some of the internal API, and I got it working for the materialization point of view.

https://gist.github.com/Antaris/ca0f271311fdb84ef3a8db95b149a307

But, because my struct isn't immutable, the state manager doesn't pick up the change.

After playing around with it, I hacked in an ISnapshotable<T> interface which essentially cloned the value for the snapshot and then it works, but couldn't do that through a replaceable EF service (had to mod SnapshotFactoryFactory but as a proof of concept - quite happy with it.

Next stop - query expressions!

@Antaris

This comment has been minimized.

Show comment
Hide comment
@Antaris

Antaris Oct 11, 2016

Updated my Gist, got expressions working but was definately tricky.

My TagSet implements ICollection<TagSet> and the ICollection<T>.Contains method is special cased in re-linq as a result operator (unlike string.Contains), so had to rename my method Exists and created a custom method translator for it.

Seems to work nicely.

Antaris commented Oct 11, 2016

Updated my Gist, got expressions working but was definately tricky.

My TagSet implements ICollection<TagSet> and the ICollection<T>.Contains method is special cased in re-linq as a result operator (unlike string.Contains), so had to rename my method Exists and created a custom method translator for it.

Seems to work nicely.

@bjorn-ali-goransson

This comment has been minimized.

Show comment
Hide comment
@bjorn-ali-goransson

bjorn-ali-goransson 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?

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.

Show comment
Hide comment
@marchy

marchy 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

marchy 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.

Show comment
Hide comment
@hidegh

hidegh 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.

hidegh 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.

Show comment
Hide comment
@marchy

marchy 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.

marchy 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.

Show comment
Hide comment
@ajcvickers

ajcvickers Feb 17, 2017

Member

@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.

Member

ajcvickers 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.

Show comment
Hide comment
@hidegh

hidegh 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...

hidegh 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.

Show comment
Hide comment
@jnm2

jnm2 Feb 17, 2017

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

jnm2 commented Feb 17, 2017

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

@hidegh

This comment has been minimized.

Show comment
Hide comment
@hidegh

hidegh 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.

hidegh 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.

Show comment
Hide comment
@jnm2

jnm2 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.

jnm2 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.

Show comment
Hide comment
@bjorn-ali-goransson

bjorn-ali-goransson Feb 18, 2017

@andez2000

This comment has been minimized.

Show comment
Hide comment
@andez2000

andez2000 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.

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;
    }
}
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment