Background
I am changing my LINQ-to-SQL code in my project to Entity Framework. Most of the change over was relatively simple, however, I have run into
It can be done in a fairly simple way but takes some manual effort. Here is an MSDN post on handling stored procedures with multiple result sets which shows both a code first and database first approach.
Example:
Load EntityB proc:
create proc dbo.Get_EntityB_by_EntityAId( @aId int )
as
select distinct
b.EntityBId
, b.Description
from
EntityA a
left outer join EntityB b
on a.PrimaryEntityB_EntityBId = b.EntityBId
left outer join EntityB b2
on a.AlternativeEntityB_EntityBId = b2.EntityBId
where
a.EntityAId = @aId
go
Load EntityA proc (which calls load B proc)
create proc dbo.Get_EntityA_by_Id( @id int )
as
-- use a select statement
select
a.EntityAId
, a.Description
, a.PrimaryEntityB_EntityBId
, a.AlternativeEntityB_EntityBId
from
EntityA a
where
a.EntityAId = @id
-- and/or other sprocs
exec dbo.Get_EntityB_by_EntityAId @id
go
Entity classes
[Table("EntityA")]
public partial class EntityA
{
public int EntityAId { get; set; }
public string Description { get; set; }
public virtual EntityB PrimaryEntityB { get; set; }
public virtual EntityB AlternativeEntityB { get; set; }
}
[Table("EntityB")]
public partial class EntityB
{
public int EntityBId { get; set; }
public string Description { get; set; }
[InverseProperty("PrimaryEntityB")]
public virtual ICollection<EntityA> EntityAsViaPrimary { get; set; }
[InverseProperty( "AlternativeEntityB" )]
public virtual ICollection<EntityA> EntityAsViaAlternative { get; set; }
}
Method that calls sproc and handles results (for this method, you could return the one EntityA
if you'd like)
public static void EagerLoadEntityA( int aId )
{
using( var db = new TestEntities() )
{
// if using code first
db.Database.Initialize( false );
var cmd = db.Database.Connection.CreateCommand();
cmd.CommandText = "dbo.Get_EntityA_by_Id";
db.Database.Connection.Open();
try
{
var reader = cmd.ExecuteReader();
var objContext = ( ( IObjectContextAdapter )db ).ObjectContext;
var aEntities = objContext
.Translate<EntityA>( reader, "EntityAs", MergeOption.AppendOnly );
reader.NextResult();
var bEntities = objContext
.Translate<EntityB>( reader, "EntityBs", MergeOption.AppendOnly );
}
finally
{
db.Database.Connection.Close();
}
}
}
Usage:
EagerLoadEntityA( 1234 );
var entityA = db.EntityAs.Find( 1234 ); // cached
var primB = entityA.PrimaryEntityB; // this is already loaded
Stored procedures cannot be directly associated with an Entity for the purposes of selecting / reading data. Typically stored procedures that are used for retrieval will return complex types and not entities. In order to interact with the DB indirectly, EF provides the ability to associate an Entity with a View for reads, and stored procedures for Insert, Update, and Delete.
Read this article for a full summary of working with EF and stored procedures. http://msdn.microsoft.com/en-us/data/gg699321.aspx
Okay, so after even further deliberation, I figured out a solution that works for what I am wanting. Since I am in a web environment and have no need to lazily load objects, I turned EnableLazyLoading to false for the entire DbContext. Then, using an EF feature called the magical relationship fix-up, I am able to do the following:
ViewModel.Model = MyDbContext.usp_ModelA_GetByID(AId).Single();
var Details =
(from b in MyDbContext.usp_ModelB_GetByID(BId)
join c in MyDbContext.usp_ModelC_GetAll()
on b.CId equals c.CId
select new ModelB()
{
BId = b.BId,
CId = b.CId,
C = c
}).ToList();
//ToList() executes the proc and projects the plate details into the object
//graph which never tries to select from the database because LazyLoadingEnabled is
//false. Then, the magical relationship fix-up allows me to traverse my object graph
//using ViewModel.Model.ModelBs which returns all of the ModelBs loaded into the graph
//that are related to my ModelA.