dotnet / vblang

The home for design of the Visual Basic .NET programming language and runtime library.
286 stars 63 forks source link

Entity Framework Core Visual Basic Support #268

Open KathleenDollard opened 6 years ago

KathleenDollard commented 6 years ago

Entity Framework has several parts. The parts that run in production work in both Visual Basic and C#. However, two design time components work only in C# out of the box: migrations and reverse engineering (creating "Code First" classes from an existing database).

The design explicitly welcomes other languages if they create some language specific components. These components are delivered via NuGet, and once those are present Visual Basic will "just work" with these aspects of Entity Framework Core.

@bricelam has created a repository for this work. There is already work being done. Brice is doing this as a maintainer and a "nights and weekends" project. He doesn't know VB well enough to get the code right, but he does know EF and is very willing to help the community get this done and done right.

Visual Basic and it's ecosystem are open source. This is an opportunity for the community to step in and make a real difference to the future of Visual Basic and show that OSS works in the Visual Basic ecosystem. Please consider helping this project move forward.

As it nears completion Brice and I will look for the right permanent location for this code. Feel free to contribute your thoughts on that in this thread, and ww will discuss specifics in Brice's repo as we get closer.

AnthonyDGreen commented 6 years ago

@InteXX, who had expressed interest to me on this over email.

InteXX commented 6 years ago

@AnthonyDGreen — Thank you, Anthony. I hope all is going well for you.

InteXX commented 6 years ago

@KathleenDollard: Thank you for this post. It's encouraging to hear from you.

Do you feel that creation of a repo similar to @bricelam's (with similar goals) would be appropriate for adding VB.NET support to MVC Razor in .NET Core?

KathleenDollard commented 6 years ago

@InteXX

At the moment I am being conservative about what I suggest because I don't want people to be frustrated more than necessary by not having things merged. @bricelam is part of the EF team and was part of explicitly designing it for extensibility to additional languages. The EF generator project is to create a part EF had intended to be open and community driven.

The MVC team believes that Razor can't be just extended to VB. I have not gone in to that code, so I am trusting the folks that wrote the code. Like the compilers, MVC was designed during a period with different base assumptions, and VB support isn't an expected extension because the parallel was expected. Now, it is quite sad.

I will certainly bring the right folks to the table if someone finds a way to do Razor or anything else. Of course people can work on all the creative things they want. But I'm only going to directly encourage people to work on projects if I am confident can be successful. I'm not discouraging the others. Does that make sense?

InteXX commented 6 years ago

@KathleenDollard

Yes.

I've since read some of your other posts discussing the difficulty associated with bringing this to light. Given your discoveries, I'm inclined to take the road of caution on this one.

bandleader commented 6 years ago

@KathleenDollard Wow, I would love to be involved in this (I wrote my own ORM that generates VB code a while back, which I still use all the time), but can't at the moment.

@KathleenDollard @bricelam To make it easier for prospective contributors, could you link to

  1. the equivalent C# generator code in EF Core, and as well as to
  2. an example C# scaffolded file generated by it?
KathleenDollard commented 6 years ago

@bandleader: Suggest you work with @bricelam on a readme.md for the EF repo so it gives key information, including other links like to guidelines. I suspect an issue on that repo could come fairly easily to a combined vision for a readme, which would really help out.

bricelam commented 6 years ago

The issue bricelam/EFCore.VisualBasic#3 links to the C# implementation. The easiest way to see code is to point it at a sample database like WideWorldImporters, AdventureWorks, Northwind, Chinook, etc.

MPCoreDeveloper commented 6 years ago

I will follow this and step in when I can

I currently use EF Core in a new project with VB Models , I just use the tools in C# and then rewrite them to VB.Net actually that`s really easy,, the tricky part is the "OnModelCreating" code

The syntax in VB with the difference in method and function lambda`s ( who did that !? LOL ) makes it sometimes hard to see what to write .

example ?

P.S. Note the use of NameOf in the VB syntax :-) as I code with strict, infer and explicit switched on

C#

  protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<CountryCode>(entity =>
            {
                entity.HasKey(e => e.CountryId);

                entity.Property(e => e.CountryId).ValueGeneratedOnAdd();

                entity.Property(e => e.CountryIso31661)
                    .IsRequired()
                    .HasColumnName("CountryISO3166-1")
                    .HasColumnType("char(2)");
            });

            modelBuilder.Entity<MeasuredProperty>(entity =>
            {
                entity.HasKey(e => new { e.MeterOutputTypeId, e.DateTime })
                    .ForSqlServerIsClustered(false);

                entity.Property(e => e.DateTime).HasColumnType("smalldatetime");

                entity.Property(e => e.Value)
                    .IsRequired()
                    .HasMaxLength(20);

                entity.HasOne(d => d.MeterOutputType)
                    .WithMany(p => p.MeasuredProperty)
                    .HasForeignKey(d => d.MeterOutputTypeId)
                    .HasConstraintName("FK_MeasuredProperty_MeterOutputType");
            });

            modelBuilder.Entity<MeasuredValue>(entity =>
            {
                entity.HasKey(e => new { e.MeteroutputTypeId, e.DateTime })
                    .ForSqlServerIsClustered(false);

                entity.Property(e => e.DateTime).HasColumnType("smalldatetime");

                entity.Property(e => e.Value).HasColumnType("numeric(11, 3)");

                entity.HasOne(d => d.MeteroutputType)
                    .WithMany(p => p.MeasuredValue)
                    .HasForeignKey(d => d.MeteroutputTypeId)
                    .HasConstraintName("FK_MeasuredValue_MeterOutputType");
            });

            modelBuilder.Entity<MeasuredValueHighFract>(entity =>
            {
                entity.HasKey(e => new { e.MeteroutputTypeId, e.DateTime })
                    .ForSqlServerIsClustered(false);

                entity.Property(e => e.DateTime).HasColumnType("smalldatetime");

                entity.Property(e => e.Value).HasColumnType("numeric(26, 18)");

                entity.HasOne(d => d.MeteroutputType)
                    .WithMany(p => p.MeasuredValueHighFract)
                    .HasForeignKey(d => d.MeteroutputTypeId)
                    .HasConstraintName("FK_MeasuredValueHighFract_MeterOutputType");
            });

            modelBuilder.Entity<Meter>(entity =>
            {
                entity.HasIndex(e => new { e.ArticleNumber, e.SerialNumber, e.CountryId })
                    .HasName("IX_Meter");

                entity.Property(e => e.NewestValues).HasColumnType("smalldatetime");

                entity.Property(e => e.OldestValues).HasColumnType("smalldatetime");

                entity.HasOne(d => d.Country)
                    .WithMany(p => p.Meter)
                    .HasForeignKey(d => d.CountryId)
                    .OnDelete(DeleteBehavior.SetNull)
                    .HasConstraintName("FK_Meter_MeterCountryCode");
            });

            modelBuilder.Entity<MeterOutputType>(entity =>
            {
                entity.HasOne(d => d.Meter)
                    .WithMany(p => p.MeterOutputType)
                    .HasForeignKey(d => d.MeterId)
                    .HasConstraintName("FK_MeterOutputType_Meter");
            });
        }
    }

VS

VB.NET

 Protected Overrides Sub OnModelCreating(ByVal modelBuilder As ModelBuilder)

            modelBuilder.Entity(Of CounTryCode)(Sub(entity As EntityTypeBuilder(Of CounTryCode))
                                                    entity.HasKey(Function(e) {NameOf(e.CounTryId)})
                                                    entity.[Property](Of Byte)(Function(e) e.CounTryId).ValueGeneratedOnAdd()
                                                    entity.[Property](Of String)(Function(e) e.CounTryIso31661).IsRequired(True) _
                                                    .HasColumnName("CountryISO3166-1").HasColumnType("char(2)")
                                                End Sub)

            modelBuilder.Entity(Of MeasuredProperty)(Sub(Entity As EntityTypeBuilder(Of MeasuredProperty))
                                                         Entity.HasKey(Function(e) {NameOf(e.MeterOutputTypeId), NameOf(e.DateTime)}) _
                                                         .ForSqlServerIsClustered(False)
                                                         Entity.Property(Of DateTime)(Function(e) e.DateTime) _
                                                         .HasColumnType("smalldatetime")
                                                         Entity.Property(Function(e) e.Value) _
                                                         .IsRequired() _
                                                         .HasMaxLength(20)
                                                         Entity.HasOne(Function(d) d.MeterOutputType) _
                                                         .WithMany(Function(p) p.MeasuredProperty) _
                                                         .HasConstraintName("FK_MeasuredProperty_MeterOutputType")
                                                     End Sub)

            modelBuilder.Entity(Of MeasuredValue)(Sub(Entity As EntityTypeBuilder(Of MeasuredValue))
                                                      Entity.HasKey(Function(e) {NameOf(e.MeteroutputTypeId), NameOf(e.DateTime)}) _
                                                      .ForSqlServerIsClustered(False)
                                                      Entity.Property(Of DateTime)(Function(e) e.DateTime) _
                                                      .HasColumnType("smalldatetime")
                                                      Entity.Property(Function(e) e.Value).HasColumnType("numeric(11, 3)")
                                                      Entity.HasOne(Function(d) d.MeteroutputType) _
                                                      .WithMany(Function(p) p.MeasuredValue) _
                                                      .HasForeignKey(Function(d) d.MeteroutputTypeId) _
                                                      .HasConstraintName("FK_MeasuredValue_MeterOutputType")
                                                  End Sub)

            modelBuilder.Entity(Of MeasuredValueHighFract)(Sub(Entity As EntityTypeBuilder(Of MeasuredValueHighFract))
                                                               Entity.HasKey(Function(e) {NameOf(e.MeteroutputTypeId), NameOf(e.DateTime)}) _
                                                                                           .ForSqlServerIsClustered(False)
                                                               Entity.Property(Of DateTime)(Function(e) e.DateTime) _
                                                                     .HasColumnType("smalldatetime")
                                                               Entity.Property(Function(e) e.Value).HasColumnType("numeric(26, 18)")
                                                               Entity.HasOne(Function(d) d.MeteroutputType) _
                                                               .WithMany(Function(p) p.MeasuredValueHighFract) _
                                                              .HasForeignKey(Function(d) d.MeteroutputTypeId) _
                                                              .HasConstraintName("FK_MeasuredValueHighFract_MeterOutputType")
                                                           End Sub)

            modelBuilder.Entity(Of Meter)(Sub(Entity As EntityTypeBuilder(Of Meter))
                                              Entity.HasIndex(Function(e) {NameOf(e.ArticleNumber),
                                                                           NameOf(e.SerialNumber),
                                                                           NameOf(e.CounTryId)}).HasName("IX_Meter")
                                              Entity.Property(Function(e) {NameOf(e.NewestValues)}).HasColumnType("smalldatetime")
                                              Entity.Property(Function(e) {NameOf(e.OldestValues)}).HasColumnType("smalldatetime")
                                              Entity.HasOne(Function(d) d.CounTry) _
                                              .WithMany(Function(p) p.Meter) _
                                              .HasForeignKey(Function(d) d.CounTryId) _
                                              .OnDelete(DeleteBehavior.SetNull) _
                                              .HasConstraintName("FK_Meter_MeterCountryCode")
                                          End Sub)

            modelBuilder.Entity(Of MeterOutputType)(Sub(Entity As EntityTypeBuilder(Of MeterOutputType))
                                                        Entity.HasOne(Function(d) d.Meter) _
                                                        .WithMany(Function(p) p.MeterOutputType) _
                                                        .HasForeignKey(Function(d) d.MeterId) _
                                                        .HasConstraintName("FK_MeterOutputType_Meter")
                                                    End Sub)

        End Sub

I hope above example will be of some help for some , and will also show why automatic generation would give some problems , code converters made a complete mess of the above translation , as I know the language pretty well It was easy for me ..

Regards

Michel https://www.linkedin.com/in/michelposseth/

ghost commented 5 years ago

@VBDotNetCoder Thank you very much for your input! It has helped me greately to target my project against .NET Core and VB.Net while still being able to take advantage from EF

paul1956 commented 5 years ago

@VBDotNetCoder I ran the C# sharp code through this https://github.com/paul1956/CSharpToVB translator and it produced the code below which compiles without error (haven't run it), I think it could easily be modified to add NameOf if anyone wants to play with it.

Protected Overrides Sub OnModelCreating(modelBuilder As ModelBuilder)
    modelBuilder.Entity(Of CountryCode)(Function(entity)
        entity.HasKey(Function(e) e.CountryId)

        entity.[Property](Function(e) e.CountryId).ValueGeneratedOnAdd()

        entity.[Property](Function(e) e.CountryIso31661).
            IsRequired().
            HasColumnName("CountryISO3166-1").
            HasColumnType("char(2)")
    End Function)

    modelBuilder.Entity(Of MeasuredProperty)(Function(entity)
        entity.HasKey(Function(e) New With {e.MeterOutputTypeId, e.DateTime}).
            ForSqlServerIsClustered(False)

        entity.[Property](Function(e) e.DateTime).HasColumnType("smalldatetime")

        entity.[Property](Function(e) e.Value).
            IsRequired().
            HasMaxLength(20)

        entity.HasOne(Function(d) d.MeterOutputType).
            WithMany(Function(p) p.MeasuredProperty).
            HasForeignKey(Function(d) d.MeterOutputTypeId).
            HasConstraintName("FK_MeasuredProperty_MeterOutputType")
    End Function)

    modelBuilder.Entity(Of MeasuredValue)(Function(entity)
        entity.HasKey(Function(e) New With {e.MeteroutputTypeId, e.DateTime}).
            ForSqlServerIsClustered(False)

        entity.[Property](Function(e) e.DateTime).HasColumnType("smalldatetime")

        entity.[Property](Function(e) e.Value).HasColumnType("numeric(11, 3)")

        entity.HasOne(Function(d) d.MeteroutputType).
            WithMany(Function(p) p.MeasuredValue).
            HasForeignKey(Function(d) d.MeteroutputTypeId).
            HasConstraintName("FK_MeasuredValue_MeterOutputType")
    End Function)

    modelBuilder.Entity(Of MeasuredValueHighFract)(Function(entity)
        entity.HasKey(Function(e) New With {e.MeteroutputTypeId, e.DateTime}).
            ForSqlServerIsClustered(False)

        entity.[Property](Function(e) e.DateTime).HasColumnType("smalldatetime")

        entity.[Property](Function(e) e.Value).HasColumnType("numeric(26, 18)")

        entity.HasOne(Function(d) d.MeteroutputType).
            WithMany(Function(p) p.MeasuredValueHighFract).
            HasForeignKey(Function(d) d.MeteroutputTypeId).
            HasConstraintName("FK_MeasuredValueHighFract_MeterOutputType")
    End Function)

    modelBuilder.Entity(Of Meter)(Function(entity)
        entity.HasIndex(Function(e) New With {e.ArticleNumber, e.SerialNumber, e.CountryId}).
            HasName("IX_Meter")

        entity.[Property](Function(e) e.NewestValues).HasColumnType("smalldatetime")

        entity.[Property](Function(e) e.OldestValues).HasColumnType("smalldatetime")

        entity.HasOne(Function(d) d.Country).
            WithMany(Function(p) p.Meter).
            HasForeignKey(Function(d) d.CountryId).
            OnDelete(DeleteBehavior.SetNull).
            HasConstraintName("FK_Meter_MeterCountryCode")
    End Function)

    modelBuilder.Entity(Of MeterOutputType)(Function(entity)
        entity.HasOne(Function(d) d.Meter).
            WithMany(Function(p) p.MeterOutputType).
            HasForeignKey(Function(d) d.MeterId).
            HasConstraintName("FK_MeterOutputType_Meter")
    End Function)
End Sub
hannespreishuber commented 5 years ago

today The project language 'VB' isn't supported by the built-in IModelCodeGenerator service. You can try looking for an additional NuGet package which supports this language; moving your DbContext type to a C# class library referenced by this project; or manually implementing and registering the design-time service for programming language.

VBAndCs commented 4 years ago

Hi all. Is this work complete? I have an issue with EF with VB.NET in eShopOnWeb_VB.NET as I explained here: https://github.com/dotnet/vblang/issues/510 Is this related to your work here? This is the exception I get:

InvalidOperationException: Lambda expression used inside Include is not valid. Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.ProcessInclude(NavigationExpansionExpression source, Expression expression, bool thenInclude)

Any ideas to make VB.NET classes work with EF? Or what I did wrong when translated the code fron C# to VB.NET? Thanks

zspitz commented 4 years ago

@VBAndCs For this particular expression, perhaps you ought to include either the factory methods used to generate the expression tree, or the textual tree view (see here), using this NuGet package that I've written. This would help pin down the differences between the VB.NET expression tree and the corresponding C# one, and hopefully help in resolving this issue.

For example, something like this.

InteXX commented 4 years ago

@zspitz That's impressive :-)

zspitz commented 4 years ago

@InteXX What is, and how can I make it more impressive?

InteXX commented 4 years ago

@zspitz ExpressionTreeToString. Good job on that.

What would you think about a ToString overload that accepts an Enum, so as to sidestep the Magic String problem?

zspitz commented 4 years ago

@InteXX I'm not sure how using enums would work with this. Besides, the magic strings aren't all that magical -- they're self-descriptive. Perhaps I should expose the names as public constants?

InteXX commented 4 years ago

@zspitz To keep it in context, I've created a new issue here for further discussion.

VBAndCs commented 4 years ago

I fixed the bug by a workaround. It is caused by a VB expression tree. I addressed this issue in #512 and it can be fixed. Thanks.