Correct way to pass Entity objects between layers? - entity-framework-4

I am just learning the Entity Framework and have made some good progress on incorporating it with my layered code structure. I have 2 visual layers, a business layer and a data access layer.
My problem is in passing entity object between layers. This code sample does not work:
// BLL
public static void Test1()
{
List<User> users = (from u in GetActiveUsers()
where u.ID == 1
select u).ToList<User>();
// Do something with users
}
// DAL
public static IQueryable<User> GetActiveUsers()
{
using (var context = new CSEntities())
{
return from u in context.Users
where u.Employee.FirstName == "Tom"
select u;
}
}
I get the error message The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
If I remove the using from the GetActiveUsers method, it works fine.
I know this is dangerous practice as the GC can dispose of the context at any given time and screw up my BLL.
So, what is the correct way to pass information between layers? Do I need to pass the context around as well?

Because you are returning IQueryable from your DAL, you cannot use the using statement.
The query is deferred until to are firing the query - .ToList in your BLL.
By that time, the context is disposed.
Think carefully about using IQueryable, as it is risky practice without knowing all the details.
Since you are still learning EF, i would keep it simple:
// BLL
public static void Test1()
{
List<User> users = GetActiveUsers();
var singleUser = users.SingleOrDefault(u => u.ID == 1);
// Do something with users
}
// DAL
public static ICollection<User> GetActiveUsers()
{
using (var context = new CSEntities())
{
var users = from u in context.Users
where u.Employee.FirstName == "Tom"
select u;
return users.ToList();
}
}
If you want to get a single user, create another method:
// DAL
public static User GetSingleActiveUser(int userId)
{
using (var context = new CSEntities())
{
var users = from u in context.Users
where u.Employee.UserId == userId
select u;
return users.SingleOrDefault();
}
}

Related

Adding record duplicates other object using entity framework

I am trying to add a new record in an MVC controller method using Entity framework.
When i just used "InsertOrUpdate" the audittype got duplicated. Based on the answer from Entity Framework adding record with a related object i hoped to fix it pretty qiock. This is the code I have right now:
Controller:
if (ModelState.IsValid)
{
Audit newAudit = Factory.GetNew();
newAudit.Name = model.Name;
newAudit.Deadline = model.Deadline;
newAudit.AuditType = auditTypeRepository.Find(model.SelectedAuditTypeId);
Repository.InsertOrUpdate(newAudit);
Repository.Save();
return RedirectToAction(MVC.Audits.Details(newAudit.Id));
}
Repository:
public override void InsertOrUpdate(Qdsa.WebApplications.AuditMaster.Data.Audit model)
{
if (model.Id == default(int))
{
// New entity
context.Audits.Add(model);
}
else
{
// Existing entity
model.ModifiedOn = DateTime.Now;
context.Entry(model).State = EntityState.Modified;
}
//If I leave out the code below the AuditType will be duplicated
if (model.AuditType != null)
{
context.Entry<AuditType>(model.AuditType).State = EntityState.Unchanged;
}
}
public virtual void Save()
{
context.SaveChanges();
}
So i thought I fixed the problem. However, AuditType has Child objects too. And now these childobjects get duplicated.
What is the right way to add entities with child objects which already exists?
Because the AuditType is required I can't save it without first and then update it. any suggestions?
UPDATE:
Both the AuditRepostory and the AuditTypeRepository inherit from BaseRepository which has the context as:
protected DBContext context = new DBContext ();
public virtual T Find(int id)
{
return All.SingleOrDefault(s => s.Id == id);
}
I can imagine two reasons for the problem:
Either auditTypeRepository.Find performs a no tracking query (with .AsNoTracking())
Or you are using a context instance per repository, so that Repository and auditTypeRepository are working with two different contexts which will indeed result in a duplication of the AuditType because you don't attach it to the the context that corresponds with Repository (except in the line with your comment).
If the latter is the case you should rethink your design and inject a single context instance into all repositories instead of creating it inside of the repositories.
I think the problem is from here:
newAudit.AuditType = auditTypeRepository.Find(model.SelectedAuditTypeId);
Change that like this:
newAudit.AuditTypeId = model.SelectedAuditTypeId;

How to move data access code from Controller to Repository

I have a Controller which returns a ViewModel to a View and it works just fine. I want to migrate to a Repository pattern but am having trouble getting the correct syntax in the repository. I have created the repository and the interface to it.
public interface IShippingRepository
{
IQueryable<ShippingCommHdr> All { get; }
IQueryable<ShippingCommHdr> AllIncluding(params Expression<Func<ShippingCommHdr, object>>[] includeProperties);
void InsertOrUpdate(ShippingCommHdr shippingcommhdr);
void Delete(int id);
void Save();
}
Here is the code form my Controller that I want to move to the repository:
public ViewResult ShippingSummary()
{
CPLinkEntities context = new CPLinkEntities();
var shipments =
from h in context.ShippingCommHdrs
where (h.CompletedDate == null)
join
e in context.vHr_Employees on h.CreatedBy equals e.ID
join
s in context.Shippers on h.ShipperID equals s.ShipperID
join
r in context.vAaiomsSites on h.ShipToSiteID equals r.SiteID
join
c in context.vHr_Employees on h.CreatedBy equals c.ID
join
p in context.vHr_Employees on h.FromSitePOC equals p.ID
select new
{
h.ID,
ShippedToSite = r.SiteName,
h.DateShipped,
h.EstDeliveryDate,
h.TrackingNo,
h.HeaderComments,
h.ShippingCommLI.Count,
s.Shipper,
CreatedBy = c.LastName,
FromSitePoc = p.LastName
};
var model = new List<ShippingSummaryVM>();
foreach (var h in shipments)
{
var viewModel = new ShippingSummaryVM
{
ID = h.ID,
ShippedToSite = h.ShippedToSite,
DateShipped = h.DateShipped,
EstDeliveryDate = h.EstDeliveryDate,
TrackingNo = h.TrackingNo,
FromSitePOC = h.FromSitePoc,
Shipper = h.Shipper,
HeaderComments = h.HeaderComments,
NumOrders = h.Count,
CreatedBy = h.CreatedBy,
};
model.Add(viewModel);
}
return View(model);
}
If I could get this one Controller/Repository to work, I can then migrate all the others over fairly quickly. thanks for any assistance
I'd start by adding a method definition to the repository interface for the query you need to execute. The repository can give this query a meaningful name:
public interface IShippingRepository
{
IQueryable<Shipment> GetShipments()
// ...
}
In the controller you'll need an instance of the repository. You can inject it into a constructor, or create one in a constructor, but either way the repository will need to talk to the CPLinkEntities context behind the scenes. You'll need to pass a context into the repository for the repository to work with.
public class SomeController : Controller
{
IShippingRepository _shippingRepository;
public SomeController()
{
_shippingRepository = new ShippingRepository(new CPLinkEntities());
}
public ViewResult ShippingSummary()
{
var shipments = _shippingRepository.GetShipments();
// ....
}
}
A concrete repository definition might look like the following.
public class ShippingRepository : IShippingRepository
{
CPLinkEntities _entities;
ShippingRepository (CPLinkEntities entities)
{
_entites = entities;
}
public IQueryable<Shipment> GetShipments()
{
return from ship in _entities.Ships join ... join ... select
}
}
Your controller method basically has 2 responsibilities
Run a Query
Map the results of the query into a view model
You can put that query into a repository, and then you could use an auto-mapper tool like AutoMapper or ValueInjecter to help you map the results of your query to a view model.
Your resulting controller method would simply call the repository to get a list of CPLinkEntities. Your controller method could then take those entities and then call the automapper to give you a list of ShippingSummaryVM's. I've left some implementation details, but this should give you a high level understanding of how to achieve what you are asking.
Option A: Have a stronger domain model. Your repository would responsible for loading root level domain objects and you let the underlying OR/M handle object traversal. Your controller would call a method on shipment to find shipments that are not yet completed. You'd get back a shipment object and could traverse to the related entities to get site name and other details you need for your VM
Option B: Have repositories that return all for each entity, and then do the join in a business or service layer. Entity Framework won't load all even if you say ShippingRepository.All. It only loads at the last responsible moment (when you need a materialized result). So you could have a business method that joins the "All" on each entity and filters based on completed date then returns back the result.
Option A is better but might be a lot more work.

EF Code First, how to reflect on model

In EF code first, one specifies field properties and relationships using the fluent interface. This builds up a model. Is it possible to get a reference to this model, and reflect on it?
I want to be able to retrieve for a given field, if it is required, what its datatype is, what length, etc...
You need to access the MetadataWorkspace. The API is pretty cryptic. You may want to replace DataSpace.CSpace with DataSpace.SSpace to get the database metadata.
public class MyContext : DbContext
{
public void Test()
{
var objectContext = ((IObjectContextAdapter)this).ObjectContext;
var mdw = objectContext.MetadataWorkspace;
var items = mdw.GetItems<EntityType>(DataSpace.CSpace);
foreach (var i in items)
{
foreach (var member in i.Members)
{
var prop = member as EdmProperty;
if (prop != null)
{
}
}
}
}

Entity Framework - Disconnexted Behavior in nTier

I am new to EF but I will try my best to describe the scenario. I have 3 tables in My DB namely RecommendationTopic, Recommendation and Question. Each RecommendationTopic can have multiple Recommendations and each Recommendation may have multiple questions. Assume that I already have predefined questions in my Question table.
I have one service that returns me list of questions like below:
public List<Question> FetchQuestions(int categoryID)
{
using (Entities context = new Entities())
{
questions = context.Questions.Where(i => i.ID >= 0).ToList();
}
}
I have another service which is used to create RecommendationTopic and Recommendation whose code is something like below:
public void ManageRecommendation(RecommendationTopic recommendationTopic)
{
using (Entities context = new Entities())
{
context.AddObject("RecommendationTopics", recommendationTopic);
context.SaveChanges();
}
}
My client code looks like below:
List<Question> questions;
using (QuestionServiceClient client = new QuestionServiceClient())
{
questions = client.FetchQuestions();
}
using (RecommendationServiceClient client = new RecommendationServiceClient())
{
RecommendationTopic rTopic = new RecommendationTopic();
rTopic.CategoryID = 3;
rTopic.Name = "Topic From Client";
Recommendation rCom = new Recommendation();
rCom.Text = "Dont!";
rCom.RecommendationTopic = rTopic;
rCom.ConditionText = "Some condition";
rCom.Questions.Add(questions[0]);
rCom.Questions.Add(questions[1]);
client.ManageRecommendation(rTopic);
}
Since the client makes 2 separate service calls, the context would be different for both the calls. When I try to run this and check the EF profiler, it not only generates query to insert into RecommendationTopic and Recommendation but also Question table!
I am sure this is caused due to different context for both the calls as when I execute a similar code within a single context, it works as it's supposed to work.
Question is, how do I make it work in a disconnected scenario?
My client could be Silverlight client where I need to fill a Question drop down with a separate call and save Recommendation topic in a separate call. For this reason I am using self tracking entities as well.
Any input appreciated!
-Vinod
If you are using STEs (self tracking entities) your ManageRecommendation should look like:
public void ManageRecommendation(RecommendationTopic recommendationTopic)
{
using (Entities context = new Entities())
{
context.RecommendationTopics.ApplyChanges(recommendationTopic);
context.SaveChanges();
}
}
Calling AddObject skips self tracking behavior of your entity. If you are not using STEs you must iterate through all questions and change their state to Unchanged:
public void ManageRecommendation(RecommendationTopic recommendationTopic)
{
using (Entities context = new Entities())
{
context.RecommendationTopics.AddObject(recommendationTopic);
foreach (var question in recommendationTopic.Questions)
{
context.ObjectStateManager.ChangeObjectState(recommendationTopic, EntityState.Unchanged);
}
context.SaveChanges();
}
}

Linq to SQL using Repository Pattern: Object has no supported translation to SQL

I have been scratching my head all morning behind this but still haven't been able to figure out what might be causing this.
I have a composite repository object that references two other repositories. I'm trying to instantiate a Model type in my LINQ query (see first code snippet).
public class SqlCommunityRepository : ICommunityRepository
{
private WebDataContext _ctx;
private IMarketRepository _marketRepository;
private IStateRepository _stateRepository;
public SqlCommunityRepository(WebDataContext ctx, IStateRepository stateRepository, IMarketRepository marketRepository)
{
_ctx = ctx;
_stateRepository = stateRepository;
_marketRepository = marketRepository;
}
public IQueryable<Model.Community> Communities
{
get
{
return (from comm in _ctx.Communities
select new Model.Community
{
CommunityId = comm.CommunityId,
CommunityName = comm.CommunityName,
City = comm.City,
PostalCode = comm.PostalCode,
Market = _marketRepository.GetMarket(comm.MarketId),
State = _stateRepository.GetState(comm.State)
}
);
}
}
}
The repository objects that I'm passing in look like this
public class SqlStateRepository : IStateRepository
{
private WebDataContext _ctx;
public SqlStateRepository(WebDataContext ctx)
{
_ctx = ctx;
}
public IQueryable<Model.State> States
{
get
{
return from state in _ctx.States
select new Model.State()
{
StateId = state.StateId,
StateName = state.StateName
};
}
}
public Model.State GetState(string stateName)
{
var s = (from state in States
where state.StateName.ToLower() == stateName
select state).FirstOrDefault();
return new Model.State()
{
StateId = s.StateId,
StateName = s.StateName
};
}
AND
public class SqlMarketRepository : IMarketRepository
{
private WebDataContext _ctx;
public SqlMarketRepository(WebDataContext ctx)
{
_ctx = ctx;
}
public IQueryable<Model.Market> Markets
{
get
{
return from market in _ctx.Markets
select new Model.Market()
{
MarketId = market.MarketId,
MarketName = market.MarketName,
StateId = market.StateId
};
}
}
public Model.Market GetMarket(int marketId)
{
return (from market in Markets
where market.MarketId == marketId
select market).FirstOrDefault();
}
}
This is how I'm wiring it all up:
WebDataContext ctx = new WebDataContext();
IMarketRepository mr = new SqlMarketRepository(ctx);
IStateRepository sr = new SqlStateRepository(ctx);
ICommunityRepository cr = new SqlCommunityRepository(ctx, sr, mr);
int commCount = cr.Communities.Count();
The last line in the above snippet is where it fails. When I debug through the instantiation (new Model.Community), it never goes into any of the other repository methods. I do not have a relationship between the underlying tables behind these three objects. Would this be the reason that LINQ to SQL is not able to build the expression tree right?
These are non-hydrated queries, not fully-hydrated collections.
The Communities query differs from the other two because it calls methods as objects are hydrated. These method calls are not translatable to SQL.
Normally this isn't a problem. For example: if you say Communities.ToList(), it will work and the methods will be called from the objects as they are hydrated.
If you modify the query such that the objects aren't hydrated, for example: when you say Communities.Count(), linq to sql attempts to send the method calls into the database and throws since it cannot. It does this even though those method calls ultimately would not affect the resulting count.
The simplest fix (if you truly expect fully hydrated collections) is to add ToList to the community query, hydrating it.
Try adding another repository method that looks like this:
public int CommunitiesCount()
{
get { return _ctx.Communities.Count(); }
}
This will allow you to return a count without exposing the entire object tree to the user, which is what I think you're trying to do anyway.
As you may have already guessed, I suspect that what you are calling the anonymous types are at fault (they're not really anonymous types; they are actual objects, which you are apparently partially populating in an effort to hide some of the fields from the end user).

Resources