This blog post demonstrates how to set up a WCF web service together with lazy loading Entity Framework entities. The entities will be developed using a code first approach and their metadata is defined using the fluent API. To use all these features you need to install the newest Entity Framework (version 4.3.1 as of now) as a NuGet package (search for “Entity Framework”).
In this blog we will work with a sample persistence model with two entity classes: persons and accounts. There is only one relation: Every person can have multiple accounts. The code of the two persistence classes looks as follows:
[DataContract(IsReference=true)]
public class Account
{
protected Account() { }
[DataMember]
public int ID { get; set; }
[DataMember]
public string Username { get; set; }
public string Password { get; set; }
[DataMember]
public virtual int PersonID { get; set; }
[DataMember]
public virtual Person Person { get; set; }
}
[DataContract(IsReference = true)]
public class Person
{
protected Person() { }
[DataMember]
public int ID { get; set; }
[DataMember]
public string Firstname { get; set; }
[DataMember]
public string Lastname { get; set; }
[DataMember]
public virtual ICollection<Account> Accounts { get; set; }
}
Every property with the DataMember attribute will be serialized and transmitted to the WCF client. Is is important to set IsReference to true because we can have cycles in the object tree, and missing this flag will lead to indefinite recursions while serializing. The constructor is protected to force the client of the class to use the DbSet’s Create method instead of creating object instances directly with the new operator. This way it is not possible to create instances which are not wrapped by a proxy object.
The next step is to develop the tailored database context class. In the constructor we instruct the class to create proxies and use lazy loading:
public class DataContext : DbContext
{
public DataContext()
{
Configuration.AutoDetectChangesEnabled = true;
Configuration.LazyLoadingEnabled = true;
Configuration.ProxyCreationEnabled = true;
Configuration.ValidateOnSaveEnabled = true;
}
protected override void Dispose(bool disposing)
{
Configuration.LazyLoadingEnabled = false;
base.Dispose(disposing);
}
public DbSet<Account> Accounts { get; set; }
public DbSet<Person> Persons { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Account>()
.Property(s => s.Username)
.IsRequired();
modelBuilder.Entity<Account>()
.HasRequired(p => p.Person)
.WithMany(p => p.Accounts)
.HasForeignKey(p => p.PersonID)
.WillCascadeOnDelete(true);
}
}
As you can see in the Dispose method, we need to disable lazy loading when the database context is disposed. This is required because otherwise the WCF serializer tries to lazy load all properties of the returned objects which will result in a “DataContext has been disposed”-Exception.
The next code fragment shows the web service class. By default, you need a ServiceContract attribute on the class and an OperationContract attribute on every web service operation. As you can see, there is another attribute called ApplyDataContractResolver. This attribute is required to transform the returned Entity Framework proxies into their original form (POCO object). The class can be found in the MyToolkit library (ApplyDataContractResolverAttribute.cs).
WCF will transfer all loaded navigation properties and not more. You can use the Entity Framework’s method Include to eager load navigation properties or simply access a navigation property after an object has been loaded. The latter method will lazily load the referenced object. This way you can decide in the web service operation which navigation properties are transmitted as objects and which properties are only transmitted as foreign key (example: If you don’t use Include in the sample operation GetAccount, only PersonID will be transmitted; the Person property will be null in the WCF client’s received Account object)
[ServiceContract]
public class MyService
{
[OperationContract]
[ApplyDataContractResolver]
public Account GetAccount(string username)
{
using (var ctx = new DataContext())
{
return ctx.Accounts.
Include("Person").
SingleOrDefault(a => a.Username == username).ToList();
}
}
[OperationContract]
[ApplyDataContractResolver]
public IList<Person> GetAllPersons()
{
using (var ctx = new DataContext())
return ctx.Persons.Include("Accounts").ToList();
}
}
For more information about how to implement a secure WCF web service read this blog post.
Rico Suter
SOFTWARE ENGINEERING
EDIT
.NET Code-First Entity Framework WCF web service