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

DBQuery() is obsolete in EF core 3 #19509

Closed
Joehannus opened this issue Jan 7, 2020 · 15 comments
Closed

DBQuery() is obsolete in EF core 3 #19509

Joehannus opened this issue Jan 7, 2020 · 15 comments
Labels
closed-no-further-action The issue is closed and no further action is planned. customer-reported

Comments

@Joehannus
Copy link

Why was issue #15656 closed? We are using DqQuery too for views. And we'd have to make a lot of breaking changes to a database that has been in production for more than one year. This breaking change is not acceptable.

Also - we don't use the modelbuilder for our migrations, so we don't have a solution for this in our .net core web api. based upon entity framework core (e.g. using modelBuilder.Entity().HasNoKey();). We have a separate application for master data management, based upon DevExpress XAF and asp.net web forms which uses Entiry Framework (using the normal .net framework). In other words we use regular Entity Framework for our Migrations.

Because we have a hybrid approach in which we use regular Entity Framework together with Entiry Framework Core (using DbQuery only in EF Core) this is quite the breaking issue for us.

@clockwiseq
Copy link

I support this message. We have projects that have hundreds of DbQuery entries in our DbContext that we use for views and honestly it's unacceptable to simply make the API obsolete in EF Core 3. Phase it out eventually maybe, but not just kill it.

@BenjaminAbt
Copy link

You can easily use EF Core without migration. You can also use external migration frameworks without any problems and still use ModelBuilder.
The ModelBinder is much more than just migrations.

Likewise, the EF Core product is not in charge if the software architecture is too rigid for a breaking change.
We write software: there will always be Breaking Changes.

@ajcvickers
Copy link
Member

ajcvickers commented Jan 7, 2020

@Joehannus @clockwiseq I would like to understand better why you would need to make a lot of breaking changes to your database. However, before that I think it would be useful to review some things about DbSet properties.

First, a property on the context exposing a DbSet is never required. For example, consider this:

public class BloggingContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
    }
    
    public DbSet<Blog> Blogs { get; set; }
}

public class Program
{
    public static async Task Main()
    {
        using (var context = new BloggingContext())
        {
            var blogs = context.Blogs.ToList();
        }
    }
}

This is equivalent, without using a DbSet property:

public class BloggingContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
    }
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>();    
    }
}

public class Program
{
    public static async Task Main()
    {
        using (var context = new BloggingContext())
        {
            var blogs = context.Set<Blog>().ToList();
        }
    }
}

Notice that:

  • The entity type is included in the model in OnModelCreating
  • The method Set<>() is used at runtime to get an IQueryable

The same was also true with DbQuery. This means that this code:

public class BloggingContext : DbContext
{
    public DbQuery<Blog> Blogs { get; set; }
}

public class Program
{
    public static async Task Main()
    {
        using (var context = new BloggingContext())
        {
            var blogs = context.Blogs.ToList();
        }
    }
}

Can be re-written as:

public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>().HasNoKey();
    }
}

public class Program
{
    public static async Task Main()
    {
        using (var context = new BloggingContext())
        {
            var blogs = context.Blogs.ToList();
        }
    }
}

or

public class BloggingContext : DbContext
{
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>().HasNoKey();
    }
}

public class Program
{
    public static async Task Main()
    {
        using (var context = new BloggingContext())
        {
            var blogs = context.Set<Blog>().ToList();
        }
    }
}

Also, the entity type can be mapped to a, possibly fictitious, view to avoid having migrations create a table:

modelBuilder.Entity<Blog>().HasNoKey().ToView("Blogs");

Finally, to create a property on the context that does not expose the full DbSet the query:

public class BloggingContext : DbContext
{
    public IQueryable<Blog> Blogs => Set<Blog>();
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>().HasNoKey().ToView("Blogs");
    }
}

@Joehannus @clockwiseq Coming back to the original question, can you provide details of exactly what it is that you are running into?

@clockwiseq
Copy link

clockwiseq commented Jan 8, 2020

@ajcvickers, it would appear your proposal solves my problem. I haven't tested it quite yet, but should be an easy find/replace and then test. Apparently I didn't know that you could do that with the entities. In my specific case, I have a stored procedure that returns a modified version of multiple entities like the following:

public class Vehicle
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public int Year { get; set; }
    public int Miles { get; set; }
}

and this entity:

public class Driver
{
    public Guid Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public virtual Vehicle Car { get; set; }
    public Guid Id CarId { get; set; }
}

It's a rudimentary example, but just hear me out. Now, I have a 3rd party system that manages races and which drives participated in a race. To interact with that 3rd party system (since it has no public API), I connect directly to the database via a DbContext. So that I can gain all of the ORM benefits of EF, I add a new entity to the 3rd Party DbContext as so:

public class RaceDriven
{
    public Guid VehicleId { get; set; }
    public int? ExternalSystemVehicleNum { get; set; }
    public Guid DriverId { get; set; }
    public int? ExternalSystemDriverNum { get; set; }
    public int CourseNum { get; set; }
    public int RaceNum { get; set; }
    public DateTime? OfficialRaceDate { get; set; }
    public string PlaceFinish { get; set; }
    public double MilesDriven { get; set; }
    public string Notes { get; set; }
}

To get this entity back, I have a stored procedure that looks like so:

SELECT
      er.RaceNum
    , er.RaceDate [OfficialRaceDate]
    , er.CourseId [CourseNum]
    , ed.DriverNum [ExternalSystemDriverNum]
    , d.Id [DriverId]
    -- ABBREVIATED BECAUSE I THINK YOU GET THE POINT...
FROM
    ExternalSystemDb.dbo.RACE er
        ExternalSystemDb.dbo.DRIVER ed ON 
            ed.DriverNum = er.DriverNum
        LEFT JOIN MyEFDatabase.dbo.Driver d ON 
            d.FirstName = ed.FNAME 
                AND d.LastName = ed.LNAME
        LEFT JOIN ExternalDb.dbo.TRACK et ON 
            et.TrackNum = er.TrackNum

So, my SP returns all of the fields I require in my "mock model" or "view model" of a race from both systems. This is done throughout the application and they were loaded via DbQuery instead of DbSet. If your above code will create the same result, I am happy.

@Joehannus
Copy link
Author

As I mentioned - we use basic Entity Framework in a masterdata web forms based application. We use DevExpress XAF for this, because it is very easy to implement a technical database management application with (the application is basically generated for you) - this application is used by our technical team for master data management. Basic Entity Framework is also responsible for the migrations and creation of our database (we work model first).

We use EF Core for most of the other database functionality - like a master data API which is based on ASP.Net Web API Core. At first we tried to combine Entity Framework and Entity Framework Core - but we couldn't get that to work.

Right now we don't use the fluent API in our Entity Framework projects - in both the regular Entity Framework as welll as in the Core Entity Framework projects. We use data annotations in stead. Also because the fluent API implementations for both differ to much, Thus in our Core Entity Framework we don't use the OnModelCreating method, but DbSet and DbQuery statements in stead. Those DbQuery statements are used for views on tables in Databases of a third party.

Right now we're still on version 2.1.x of EF. But with the exclusion of DbQuery we would have to do a lot of refactoring. And with all the hassle we had to get EF and EF Core to work well together, I don't even know whether it is possible to replace the data annotations by the fluent api and get everything work well together. It could mean we won't be able to use version 3.0.

@ajcvickers
Copy link
Member

@Joehannus The DbContext you use must be specific to EF Core. It must already be different from any code you have for EF6 since EF6 doesn't have DbQuery. So I'm finding it hard to understand what exactly you are blocked on. Is it just that you don't want to start using the model building API in your EF Core DbContext? Or is it that you really think you can't. If the latter, then can you describe again why.

@Joehannus
Copy link
Author

We only use DBQuery in our .Net Core Database projects. This also the only place that we need it, because they are used in our .Net Core Master Data API, to get data from views in other databases of a third party.

Most of our software is .Net Core based - like for instance Azure Functions. Web API applications etc. Data retrieval and mutations are performed using Entity Framework Core in these .Net Core applications.

We have one application however that we use for master data management. This one is Entity Framework Based. Like I mentioned - we used a (Web Forms based) framework from DevExpress to implement this with. Because it lets you generate web pages for (table based) data management automatically. The data layer projects of this application contain the migrations for the data base and the POCO entity classes with data annotations. The db context class of this project only contains dbset statements. This Framework is only available for .Net Framework, unfortunately.

Like I mentioned - we do not use the model builder (and fluent API), but data annotations in stead in our POCO classes. We always copy the POCO entity classes (with data annotations) from the Entity Framework based project to that of EF Core. The syntax of the model builder API of EF Core and EF have to many differences to get a good hybrid working situation. There aren't a lot of differences in regard to data annotations between the two frameworks.

Would it be an option to introduce a HasNoKey data annotation attribute? In addition to having the ModelBuilder / Fluent API HasNoKey property?

@ajcvickers
Copy link
Member

@Joehannus That is covered by #19246

@Joehannus
Copy link
Author

@Joehannus That is covered by #19246

Any idea when this will be implemented. We're probably going to update our .Net Core software from 2 to 3. And upgrading EF Core will be necessary as well then.

The fact that this is not available yet will not let us upgrade our .Net Core Software, because the DbQuery views are a very important part of our software solution, functionally.

@ajcvickers
Copy link
Member

@Joehannus Can you explain again why you can't use OnModelCreating on the EF Core context that previously had DbQuery properties?

@Joehannus
Copy link
Author

Joehannus commented Jan 27, 2020

Sorry for the late reaction - we've been quite busy here in our project. See below for my answer.

Because we use (basic) Entity Framework in an asp.net web forms application for our base master data management. We use the DevExpress XAF framework for this application. Because it allows for easy automatich generation of Web forms pages based upon the Entity Framework entity classes with data annotations. Migrations are part of this project.

We use Entity Framework Core in our master data API, but no migrations. To keep it simple and easy, we copy the Entity Framework entity classes (with data annotations) from the master data application EF project. And use DbSet and DbQuery statements to set up the "pipeline".

I don't think XAF allows for the fluent API, using the modelbuilder syntax. But even if that were the case - the fluent API of Entity Framework and Entity Framework are to different (syntax wise) to get a good working hybrid implementation, like data annotations allow for. At least that's our experience.

What I wonder though - about this NoKey attribute. What does this exactly do? Is its only purpose not to allow for the creation of tables? Will it allow data mutations? Because that's what we use DbQuery for - only data retrieval based upon views. As I mentioned before - the views are to an external databases of a third party supplier. Data mutations should not be able in these databases. We did limit this ourselves by using separate GenericRepositories for both situations. But it shouldn't be possible from the basic entity framework "setup"/"implementation" either.

@ajcvickers
Copy link
Member

@Joehannus I still don't understand at all what is blocking you. I think the only way we're going to make progress here is if you can show in code what you mean. For example, put together a small project that demonstrates what you are doing so we can talk in concrete terms about other ways to do it.

@ajcvickers
Copy link
Member

EF Team Triage: Closing this issue as the requested additional details have not been provided and we have been unable to reproduce it.

BTW this is a canned response and may have info or details that do not directly apply to this particular issue. While we'd like to spend the time to uniquely address every incoming issue, we get a lot traffic on the EF projects and that is not practical. To ensure we maximize the time we have to work on fixing bugs, implementing new features, etc. we use canned responses for common triage decisions.

@ajcvickers ajcvickers added closed-no-further-action The issue is closed and no further action is planned. and removed type-bug labels Feb 7, 2020
@AccessViolator
Copy link

ToView() hack is both ugly AND undocumented.
At least create and document a sensible shortcode, e.g. NoTable() that would do
.HasNoKey().ToView(Guid.NewGuid().ToString());

@AndriySvyryd
Copy link
Member

AndriySvyryd commented Jul 10, 2020

@AccessViolator In EF 5.0 you'll be able to call ToTable("blah", excludedFromMigrations: true) to map the entity type to a table that's not managed by Migrations or ToTable(null) to avoid mapping it to anything (this effectively makes it read-only)

@ajcvickers ajcvickers reopened this Oct 16, 2022
@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-no-further-action The issue is closed and no further action is planned. customer-reported
Projects
None yet
Development

No branches or pull requests

6 participants