21

I am working on a new application that requires, the creation of DB Views. I am using EF core with MySql flavor and using the Code First approach to create the DB and tables. I didn't find a way to create a view though.If somehow, I could execute the raw sql in migration step, that might help in creation of view and later mapping that as the DbSet. I can't create the views manually as it would require the execution of the view script against the database, and that won't be possible in higher environment. Can someone please guide me. Help is really appreciated!

1
  • migrationBuilder.Sql(raw_sql); Commented Nov 4, 2019 at 13:06

4 Answers 4

25

As far as I know, you can't create views with EF Core directly. However, you can use migrations to execute arbitrary SQL when upgrading.

  1. Generate a migration in the package manager console, even if there is nothing really to do: add-migration CreatingTheView
  2. Open the generated migration class, file name: Migrations\yyyyMMdd_CreatingTheView.cs
  3. Add raw SQL to execute when upgrading by modifying the Up method: migrationBuilder.Sql("CREATE VIEW etc.");
  4. If you want to support downgrading, modify the Down method: migrationBuilder.Sql("DROP VIEW etc.");
Sign up to request clarification or add additional context in comments.

Comments

8

The answer from relatively_random almost worked for me, but I had to use this syntax to get it working:

migrationBuilder.Sql(@"exec('create view dbo.MyViewName as ....');");

and

migrationBuilder.Sql("exec('drop view dbo.MyViewName');");

Without that I got a sql error "Create View must be the only statement in batch".

3 Comments

to add on this, on .net ef core; this one worked for me: migrationBuilder.Sql($"EXEC sp_executesql N'{sql.Replace("'", "''")}'");
also, there should be only one view creation per sql exec . I was trying to have multiple view creations in this excel and got following error: Unclosed quotation mark after the character string 'CREATE VIEW. However my view had no syntax errors.
I saw this answer and I tried it first, but ef will have none of it and complains about syntax error. If I do the relatively_random answer version it works fine. I did create separate calls to the migrationBuilder method (one for each view), I don't know if that has anything to do with it
7

Since you're using Code First, maybe the database is just an extension to the application, and not a primary goal of its own? In that case, you probably don't really need a view.

The database won't care if you just send it normal queries, and you have alternatives for abstracting them in the application layer. The most basic hack is to just create an IQueryable<T> property in your DbContext implementation (or an extension method), which queries the data you wish displayed. Something like this:

public sealed class DatabaseContext : DbContext
{
    public DbSet<Transaction> Transactions { get; set; }

    public IQueryable<PerPersonSum> PerPersonSums
        => Transactions.GroupBy(t => t.Person,
                                (k, g) => new PerPersonSum
                                {
                                    Person = k,
                                    TotalAmount = g.Sum(t => t.Amount)
                                });
}

A more proper solution would be a keyless entity type:

public sealed class DatabaseContext : DbContext
{
    public DbSet<Transaction> Transactions { get; set; }

    public DbSet<PerPersonSum> PerPersonSums { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<PerPersonSum>(e =>
        {
            e.HasNoKey();
            e.ToQuery(() => Transactions.GroupBy(t => t.Person,
                                                 (k, g) => new PerPersonSum
                                                 {
                                                     Person = k,
                                                     TotalAmount = g.Sum(t => t.Amount)
                                                 }));
        });
    }
}

Pre-3.0, it used to be called a Query Type and it could be used like this:

public sealed class DatabaseContext : DbContext
{
    public DbSet<Transaction> Transactions { get; set; }

    public DbQuery<PerPersonSum> PerPersonSums { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Query<PerPersonSum>()
                    .ToQuery(() => Transactions.GroupBy(t => t.Person,
                                                        (k, g) => new PerPersonSum
                                                        {
                                                            Person = k,
                                                            TotalAmount = g.Sum(t => t.Amount)
                                                        }));
    }
}

5 Comments

i really like this solution, feel like someone should be telling me why this is bad lol
@workabyte Depends on the purpose of the project. Is the application the point of the project, with data serving it? Or is data the focus, with application serving the data? I usually have data serving the application so I almost never use views (which si why I almost always use SQLite instead of a "real" RDBMS). If the database is the real product and is expected to survive longer than the application, views are probably preferred. On the other hand, in that case, going code-first is probably a mistake as well.
@relatively_random Some databases do not treat views as saved querys and can optimize them more. Shouldn't that be a consideration as well?
@InbarBarkai Sure sounds like it. Unfortunately, I'm not exactly an expert on databases and most of my experience comes from EFC with SQLite. Is this is a standard feature on basically all traditional database engines? SQLite really treats views as little more than text which gets copy-pasted into queries. At least it did last time I checked.
@relatively_random As far a I know, Oracle and PostgreSQL are actually doing some optimizations on views. Maybe Microsoft SQL server do that as well but I did not work with it for quite some time.
0

It's not directly supported (yet), but all the building blocks are already there. Essentially, a view is the same as "projection", which EF already supports. We can even get the SQL for it, and make EF map a view to a DbSet. We just have to create the view ourselves.

1. Design your view entity

For example:

public class UserStatsView
{
    public bool IsActive { get; set; }
    public int Count { get; set; }
}

2. Define the view and map to a DbSet
Define a separate DbSet for this view and map it inside OnModelCreating. Also, design the query which represents the "body" of the view:

public class AppDbContext : DbContext
{
    
    public DbSet<User> Users { get; set; } // "real" table
    public DbSet<UserStatsView> UserStats { get; set; } // mapped to our view

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<UserStatsView>()
            .ToView("View_UserStats")
            .HasNoKey(); // Required if the view has no primary key
    }

    // the query that represents your view
    public IQueryable<UserStatsView> GetUserStatsQuery() => this.Users
        .GroupBy(u => u.IsActive)
        .Select(g => new UserStatsView
        {
            IsActive = g.Key,
            Count = g.Count()
        });
}

3. Create the view in a migration

Get the raw SQL for the query from your DbContext and use it to create the view manually inside a migration.

protected override void Up(MigrationBuilder migrationBuilder)
{
    // at design-time only!
    using var context = new AppDbContext(); 

    // get the raw sql of the query
    var sql = context.GetUserStatsQuery().ToQueryString();

    // create the view
    migrationBuilder.Sql($"""
        CREATE VIEW View_UserStats AS
        {sql}
        """);
}

protected override void Down(MigrationBuilder migrationBuilder)
{
    migrationBuilder.Sql("DROP VIEW IF EXISTS View_UserStats");
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.