Chapter 6. Working with Views, Stored Procedures, the Asynchronous API, and Concurrency

In this chapter, you will learn how to integrate Entity Framework with additional database objects, specifically views and stored procedures. We will see how to take advantage of existing stored procedures and functions to retrieve and change the data. You will learn how to persist changed entities from our context using stored procedures. We will gain an understanding of the advantages of asynchronous processing and see how Entity Framework supports this concept via its built-in API. Finally, you will learn why concurrency is important for a multi-user application and what options are available in Entity Framework to implement optimistic concurrency.

In this chapter, we will cover how to:

  • Get data from a view
  • Get data from a stored procedure or table-valued function
  • Map create, update, and delete operations on a table to a set of stored procedures
  • Use the asynchronous API to get and save the data
  • Implement multi-user concurrency handling

Working with views

Views in an RDBMS fulfill an important role. They allow developers to combine data from multiple tables into a structure that looks like a table, but do not provide persistence. Thus, we have an abstraction on top of raw table data. One can use this approach to provide different security rights, for example. We can also simplify queries we have to write, especially if we access the data defined by views quite frequently in multiple places in our code. Entity Framework Code-First does not fully support views as of now. As a result, we have to use a workaround. One approach would be to write code as if a view was really a table, that is, let Entity Framework define this table, then drop the table, and create a replacement view. We will still end up with strongly typed data with full query support. Let's start with the same database structure we used before, including person and person type. Our view will combine a few columns from the Person table and Person type name, as shown in the following code snippet:

public class PersonViewInfo
{
    public int PersonId { get; set; }
    public string TypeName { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

Here is the same class in VB.NET:

Public Class PersonViewInfo
    Public Property PersonId() As Integer
    Public Property TypeName() As String
    Public Property FirstName() As String
    Public Property LastName() As String
End Class

Now, we need to create a configuration class for two reasons. We need to specify a primary key column because we do not follow the naming convention that Entity Framework assumes for primary keys. Then, we need to specify the table name, which will be our view name, as shown in the following code:

public class PersonViewInfoMap :
    EntityTypeConfiguration<PersonViewInfo>
{
    public PersonViewInfoMap()
    {
        HasKey(p => p.PersonId);
        ToTable("PersonView");
    }
}

Here is the same class in VB.NET:

Public Class PersonViewInfoMap
    Inherits EntityTypeConfiguration(Of PersonViewInfo)
    Public Sub New()
        HasKey(Function(p) p.PersonId)
        ToTable("PersonView")
    End Sub
End Class

Finally, we need to add a property to our context that exposes this data, as shown here:

public DbSet<PersonViewInfo> PersonView { get; set; }

The same property in VB.NET looks quite familiar to us, as shown in the following code:

Property PersonView() As DbSet(Of PersonViewInfo)

Now, we need to work with our initializer to drop the table and create a view in its place. We are using one of the initializers we created before. When we cover migrations, we will see that the same approach works there as well, with virtually identical code. Here is the code we added to the Seed method of our initializer, as shown in the following code:

public class Initializer : DropCreateDatabaseIfModelChanges<Context>
{
    protected override void Seed(Context context)
    {
        context.Database.ExecuteSqlCommand("DROP TABLE PersonView");
        context.Database.ExecuteSqlCommand(
            @"CREATE VIEW [dbo].[PersonView]
            AS
            SELECT
              dbo.People.PersonId,
              dbo.People.FirstName,
              dbo.People.LastName,
              dbo.PersonTypes.TypeName
            FROM    
              dbo.People
            INNER JOIN dbo.PersonTypes
              ON dbo.People.PersonTypeId = dbo.PersonTypes.PersonTypeId
            ");
    }
}

In the preceding code, we first drop the table using the ExecuteSqlCommand method of the Database object. This method is useful because it allows the developer to execute arbitrary SQL code against the backend. We call this method twice, the first time to drop the tables and the second time to create our view.

The same initializer code in VB.NET looks as follows:

Public Class Initializer
    Inherits DropCreateDatabaseIfModelChanges(Of Context)
    Protected Overrides Sub Seed(ByVal context As Context)
        context.Database.ExecuteSqlCommand("DROP TABLE PersonView")
        context.Database.ExecuteSqlCommand( <![CDATA[
            CREATE VIEW [dbo].[PersonView]
            AS
            SELECT
              dbo.People.PersonId,
              dbo.People.FirstName,
              dbo.People.LastName,
              dbo.PersonTypes.TypeName
            FROM    
              dbo.People
            INNER JOIN dbo.PersonTypes
              ON dbo.People.PersonTypeId = dbo.PersonTypes.PersonTypeId]]>.Value())
    End Sub
End Class

Since VB.NET does not support multiline strings such as C#, we are using XML literals instead, getting a value of a single node. This just makes SQL code more readable.

We are now ready to query our data. This is shown in the following code snippet:

using (var context = new Context())
{
    var people = context.PersonView
        .Where(p => p.PersonId > 0)
        .OrderBy(p => p.LastName)
        .ToList();
    foreach (var personViewInfo in people)
    {
        Console.WriteLine(personViewInfo.LastName);
    }

As we can see, there is literally no difference in accessing our view or any other table. Here is the same code in VB.NET:

Using context = New Context()
    Dim people = context.PersonView _
            .Where(Function(p) p.PersonId > 0) _
            .OrderBy(Function(p) p.LastName) _
            .ToList()
    For Each personViewInfo In people
        Console.WriteLine(personViewInfo.LastName)
    Next
End Using

Tip

Although the view looks like a table, if we try to change and update an entity defined by this view, we will get an exception.

If we do not want to play around with tables in such a way, we can still use the initializer to define our view, but query the data using a different method of the Database object, SqlQuery. This method has the same parameters as ExecuteSqlCommand, but is expected to return a result set, in our case, a collection of PersonViewInfo objects, as shown in the following code:

using (var context = new Context())
{
    var sql = @"SELECT * FROM PERSONVIEW WHERE PERSONID > {0} ";
    var peopleViaCommand = context.Database.SqlQuery<PersonViewInfo>(
        sql,
        0);
    foreach (var personViewInfo in peopleViaCommand)
    {
        Console.WriteLine(personViewInfo.LastName);
    }
}

The SqlQuery method takes generic type parameters, which define what data will be materialized when a raw SQL command is executed. The text of the command itself is simply parameterized SQL. We need to use parameters to ensure that our dynamic code is not subject to SQL injection. SQL injection is a process in which a malicious user can execute arbitrary SQL code by providing specific input values. Entity Framework is not subject to such attacks on its own. Here is the same code in VB.NET:

Using context = New Context()
    Dim sql = "SELECT * FROM PERSONVIEW WHERE PERSONID > {0} "
    Dim peopleViaCommand = context.Database.SqlQuery(Of PersonViewInfo)(sql, 0)
        For Each personViewInfo In peopleViaCommand
        Console.WriteLine(personViewInfo.LastName)
    Next
End Using

We not only saw how to use views in Entity Framework, but saw two extremely useful methods of the Database object, which allows us to execute arbitrary SQL statements and optionally materialize the results of such queries. The generic type parameter does not have to be a class. You can use the native .NET type, such as a string or an integer.

Tip

It is not always necessary to use views. Entity Framework allows us to easily combine multiple tables in a single query.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.27.251