How to update eagerly loaded entity with relations - asp.net-mvc

I am using EF4 with ASP.Net MVC, and I wonder how am I to update entity which was loaded eagerly?
When I load the entity as follows:
static readonly Func<Entities, Guid, IQueryable<Call>> CallByGuid =
CompiledQuery.Compile<Entities, Guid, IQueryable<Call>>(
(db, callGuid) =>
db.Calls
.Where(c => !c.Removed && c.CallGUID.Equals(callGuid)));
public Call GetCall(Guid guid)
{
return CallByGuid.Invoke(_db, guid).Single();
}
and pass the entity to the View, edit it there and return back to Controller and save it as follows:
public Call UpdateCall(Call call)
{
_db.ObjectStateManager.ChangeObjectState(call, EntityState.Modified);
_db.Call_VoiceCall.ApplyCurrentValues(call);
_db.SaveChanges();
Call dbCall = FindCall(call.CallGUID);
return dbCall;
}
everything works fine, though quite slow. When I load the entity as follows:
static readonly Func<Entities, Guid, IQueryable<Call>> CallByGuid =
CompiledQuery.Compile<Entities, Guid, IQueryable<Call>>(
(db, callGuid) =>
db.Calls
.Include("Call_VoiceCall")
.Include("Call_Rating")
.Include("Memos")
.Include("Company")
.Include("PhoneService")
.Where(c => !c.Removed && c.CallGUID.Equals(callGuid)));
public Call GetCall(Guid guid)
{
return CallByGuid.Invoke(_db, guid).Single();
}
the list of entities loads very fast, but I cannot update an entity: what I get is
"Violation of PRIMARY KEY constraint 'PK_Call_5599FCED3AE4474F'.
Cannot insert duplicate key in object 'dbo.Call'. The duplicate key
value is (6d078f37-29e6-4e56-9853-d793994ce163). The statement has
been terminated."
or something like this.
Now, since it is almost impossible to google questions on the similar topic, I am doing something seriously wrong. What is it?

Related

Repository pattern giving exception while updating record

In my MVC application, I have been using Repository pattern for DAL.
Now, when I do select one entity record and and update the entity field value and do Update operation then getting below error.
Attaching an entity of type 'DAL.User' failed because another entity
of the same type already has the same primary key value. This can
happen when using the 'Attach' method or setting the state of an
entity to 'Unchanged' or 'Modified' if any entities in the graph have
conflicting key values. This may be because some entities are new and
have not yet received database-generated key values. In this case use
the 'Add' method or the 'Added' entity state to track the graph and
then set the state of non-new entities to 'Unchanged' or 'Modified' as
appropriate."} System.Exception
Below is repository stuff:
public void Update(TEntity entity)
{
if (_context.Entry(entity).State != EntityState.Modified)
{
_dbSet.Attach(entity);
_context.Entry(entity).State = EntityState.Modified;
}
}
Calling as follow:
In Bussines layer library:
Manager class :
private readonly IUnitOfWork _unitOfWork;
private IRepository <User , int> UserRepository
{
get
{
return _unitOfWork.GetRepository<AccountUser, int>();
}
}
public void UpdateUserEntity(UserDTO u)
{
try
{
User model = new User ();
UserRepository.Update(Mapper.Map(u, model));
_unitOfWork.SaveChanges();
}
catch (Exception ex)
{
throw;
}
}
Please guide me how I could resolve above error.
The exception says that there is another entity with the same key that has been attached, but different reference.
The exception could be caused by previous attached entity.
db.Set<Entity>().Attach(new Entity { Id = 123 });
db.Set<Entity>().Attach(new Entity { Id = 123 }); // different reference but same key
Or could be also caused by tracked entity that automatically attached.
db.Set<Entity>().FirstOrDefault(e => e.Id == 123); // automatically attached
db.Set<Entity>().Attach(new Entity { Id = 123 }); // different reference but same key
The second cause can be solved by mentioning AsNoTracking when retrieving item.
db.Set<Entity>().AsNoTracking().FirstOrDefault(e => e.Id == 123);
Or to be safe you can use this extension to always detach any attached entity.
public static class DbSetExtension
{
public static void SafeAttach<T>(
this DbContext context,
T entity,
Func<T, object> keyFn) where T : class
{
var existing = context.Set<T>().Local
.FirstOrDefault(x => Equals(keyFn(x), keyFn(entity)));
if (existing != null)
context.Entry(existing).State = EntityState.Detached;
context.Set<T>().Attach(entity);
}
}
Usage.
db.SafeAttach(entity, e => e.Id);
It's because of the reason,
"TEntity entity as a new object instead of the one which already exists".
Means,Entity framework treats each new object as new entry.(eventhough with same existing old data,PK & all).
Solution is,
First retrieve the object from database
Do/assign the changes to the same object (preferably without changing Primary key)
Then do state as Modified ,Update,SaveChange()

Selecting only part of the DbSet in Read method of Kendo grid

I'm trying to use a Kendo UI grid in MVC and remote data. I want to only grab and display data from the DbSet, onload, where one of the fields, "Status", equals '1'. I thought this should be able to be accomplished in the controller:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Read([DataSourceRequest] DataSourceRequest request)
{
using (var db = new MyData(false))
{
var data = db.Training.Where(d => d.Status == '1').Select(d => new Training {
Id = d.Id,
Name = d.Name,
Status = d.Status
}).ToDataSourceResult(request);
return Json(data, JsonRequestBehavior.AllowGet);
}
}
The above code gives me the error that "The entity or complex type 'Training' cannot be constructed in a LINQ to Entities query". Any suggestions on how to rewrite the Linq statement so it'll work, or maybe a way to do it within the grid to suppress any that do not have a Status of '1'?
Your code is trying to project to a mapped entity which is not allowed. Additionally it's redundant to do that as you already have your entities. Remember that .Select() is for mapping one type to another but the .Where() method is already returning a list of your entities (Training).
Remove the .Select() and the query should work:
var data = db.Training.Where(d => d.Status == '1').ToDataSourceResult(request);

Insert to multiple foreign key tables in MVC with super-type/sub-type database-first model

I have two sub-types of a super-type "Entity", namely "Household" and "Involved Body".
I've modeled them as shown below in my database and they were auto-generated to the EF Model (again shown below).
database
edmx model
Using the default scaffolding for MVC I am able to add a new Household without any problems. However, when I try to add a new Involved Body I hit an error when it tries to add the Entity Type.
There only relevant (as far as I can tell) difference between the two sub-types is that the EntityType for a Household is hard-coded as "Household" whereas the EntityType for an Involved Body can be any EntityType except "Household" - this is selected from a list by the user.
The Create Action on the HTTP POST for the Involved Body throws an error relating to the foreign key between tEntity and tEntityType with the tEntityType being null. Code as follows:
[HttpPost]
public ActionResult Create([Bind(Exclude = "entityID")]tEntity tentity
, tInvolvedBody tinvolvedbody
, tAddress taddress
, tAddressEntity taddressentity
//, tEntityType tentitytype
, int entityTypeID
)
{
#region entity type
//find entity type from id
var tentitytype = db.tEntityTypes.Find(entityTypeID);
#endregion
#region address
//assume start date of involved body not needed for reporting
taddressentity.startDate = DateTime.Now.Date;
#endregion
if (ModelState.IsValid)
{
db.tEntities.Add(tentity);
db.tInvolvedBodies.Add(tinvolvedbody);
db.tAddresses.Add(taddress);
db.tAddressEntities.Add(taddressentity);
db.SaveChanges();
return RedirectToAction("Index");
}
//recreate viewbag for entityType dropdown
var q = (
from e in db.tEntityTypes
where e.entityType != "Household"
select e
);
ViewBag.entityTypeID = new SelectList(q, "entityTypeID", "entityType");
return View(tinvolvedbody);
}
I've tried adding the tEntityType to the parameters list for the create but this results in the ModelState.IsValid returning false due to the entityType being null on all the objects.
I've also tried actively linking the entity type to each of the other objects using:
tentity.tEntityType = tentitytype;
tinvolvedbody.tEntity.tEntityType = tentitytype;
taddressentity.tEntity.tEntityType = tentitytype;
The above ends up working but it creates a new Entity for each of the other objects i.e. I get three new rows in my tEntity table, one is the Entity, one links to tInvolvedBody and one links to tAddressEntities. This makes no sense...
How can I insert a new InvolvedBody that creates an Entity, picks up the Entity Type and then links to the AddressEntity junction table?
Finally worked through this. Not sure if the answer is 'perfect' from a developer perspective but it works.
After intense debugging I realised that the navigation properties for the involved body and address entity were both looking for an entitytypeID which I had assumed would be provided by the entity object.
If I passed these in directly with the code shown:
tinvolvedbody.tEntity.tEntityType = tentitytype;
taddressentity.tEntity.tEntityType = tentitytype;
...I ended up with three new entitites and no relational data existing between all of {entity, involved body, address}
The code that works removes the explicit addition of a new entity and relies on EF to create an entity from the Involved Body. I then used the newly created entityID to map the address via addressentity as follows:
[HttpPost]
public ActionResult Create([Bind(Exclude="entityID")]tEntity tentity
,tInvolvedBody tinvolvedbody
,tAddress taddress
,tAddressEntity taddressentity
,int entityTypeID
)
{
#region entity type
var t =
(
from e in db.tEntityTypes
where (e.entityTypeID == entityTypeID)
select e
);
tinvolvedbody.tEntity.tEntityType = t.First();
#endregion
#region address
//assume start date of involved body not needed for reporting
taddressentity.startDate = DateTime.Now.Date;
#endregion
if (ModelState.IsValid)
{
db.tInvolvedBodies.Add(tinvolvedbody);
db.tAddresses.Add(taddress);
taddressentity.tEntity = db.tEntities.Find(tinvolvedbody.bodyID);
db.tAddressEntities.Add(taddressentity);
db.SaveChanges();
return RedirectToAction("Index");
}
Have you tried setting the typeID specifically? Also, from what I gathered from your model, the taddress is a child of taddressentity? As such, should it not be inserted first in order for the foreign key?
if (ModelState.IsValid)
{
tentity.entityTypeID = entityTypeID;
db.tEntities.Add(tentity);
tinvolvebody.bodyID= tentity.entityID
db.tInvolvedBodies.Add(tinvolvedbody);
taddressentity.entityID = tentity.entityID;
db.tAddressEntities.Add(taddressentity);
taddress.UPRN = taddressentity.UPRN;
db.tAddresses.Add(taddress);
db.SaveChanges();
return RedirectToAction("Index");
}

cshtml runtime error: Only parameterless constructors and initializers are supported in LINQ to Entities

Want to use objects of a public class in cshtml, but got a runtime error: Only parameterless constructors and initializers are supported in LINQ to Entities. What is wrong with the following statement? Thanks for any help!
#foreach (var obj in ViewData["IncompleteList"] as IEnumerable<Games.TeamAction>)
The controller fills the ViewBag, like
IEnumerable<TeamAction> incomplete = dbIncAct.IncompleteActivity.Where(a => a.activityID == id)
.Select(s => new TeamAction(s.teamID, s.name, id, s.type));
ViewBag.IncompleteList = incomplete;
The class TeamAction (part of the namespace Games) is quite simple:
public class TeamAction
{
public TeamAction()
{
}
....
public int teamID {get; set;}
public string teamName { get; set; }
public int activityID { get; set; }
public int actionType { get; set; }
}
The answer is in the error message, once you figure out how to interpret it.
You are creating an IEnumerable via LINQ to Entities (the Entity Framework LINQ provider) like so:
IEnumerable<TeamAction> incomplete = dbIncAct.IncompleteActivity
.Where(a => a.activityID == id)
.Select(s => new TeamAction(s.teamID, s.name, id, s.type));
Note that your Select call includes a lambda expression that calls the TeamAction constructor that takes 4 parameters. As soon as you ask for the first element in the IEnumerable, LINQ tries to execute your query. At that point, it parses your lambda expression and tries to translate it into an Entity Framework query that it can run. But, as the exception message says:
Only parameterless constructors and initializers are supported
You cannot include the parameterized constructor in your LINQ query because LINQ to Entities doesn't know how to execute it. To fix the problem you have a few options.
Option One: IQueryable -> IEnumerable
The easiest way around this is to make sure the EF LINQ provider never sees the offending lambda, by forcing your IQueryable into an IEnumerable before it gets there. dbIncAct.ImcompleteActivity is probably a DbSet<>, and DbSet<>.Where returns an IQueryable that is still dependent on LINQ 2 Entities. To break that dependency, you can do:
IEnumerable<TeamAction> incomplete = dbIncAct.IncompleteActivity
.Where(a => a.activityID == id)
.AsEnumerable()
.Select(s => new TeamAction(s.teamID, s.name, id, s.type));
That will force your EF query to run up through the Where part and return an enumerable collection of IncompleteActivity entities. That thing (some internally-defined List-like object) is then used to call Select, completely apart from EF.
The downside here is that you're forcing the EF query, which probably hits a database, to happen immediately. If you don't want that, your only choice is to eliminate the parameterized constructor, using one of the other two options.
Option Two: Object Initializers
Depending on what that constructor did, you may or may not be able to easily fix it. If your constructor is just there to set properties on your newly created object, you're in luck. C# introduced the new object initializer syntax to go along with LINQ and lambdas for precisely this reason:
IEnumerable<TeamAction> incomplete = dbIncAct.IncompleteActivity
.Where(a => a.activityID == id)
.Select(s => new TeamAction
{
TeamId = s.teamID,
Name = s.name,
Id = id,
Type = s.type
});
Option Three: Refactor
If your constructor does any actual work, then you'll need to do some refactoring. Try to move as much logic into your default TeamAction() constructor as you can. You can also put some of the logic into the property setters, though you should try to minimize that as much as you can.
If your object really does require some complex initialization, the typical pattern to have an initialization method that gets called early in the life cycle, e.g:
var x = new X { ... };
x.InitializeMe();
You could do this, for example, inside your #for loop, or as a separate step immediately after you create your query.
Change it to object initialization instead:
IEnumerable<TeamAction> incomplete = dbIncAct.IncompleteActivity.Where(a => a.activityID == id)
.Select(s => new TeamAction { teamID = s.teamID, teamName = s.name, activityID = s.id, actionType = s.type });

ASP.NET MVC, LINQ and ModelBinders

Is there a pre-built ModelBinder I can use with LINQ to get an object from a DataContext and update it on a HTTP post?
For example, currently I have this block of code:
[AcceptVerbs (HttpVerbs.Post)]
public ActionResult Edit (Project project)
{
var projectService = Factory.GetService<IProjectService> ();
project = projectService.GetProject (project.ProjectId);
UpdateModel<Project> (project);
if (!ModelState.IsValid)
return View (project);
project = projectService.SaveProject (project);
return RedirectToAction ("Details", new { id = project.ProjectId });
}
(IProjectService wraps up calls to a LINQ data context)
In order to actually perform the update to the database via the LINQ data context, I need to get the project instance again and then update that instance.
Any attempt to simply save the project instance without first getting it from the data context results in nothing being written back to the database - I'm assuming because the LINQ data context knows nothing of the object it doesn't do anything with it.
Using the Attach method on the Projects table class doesn't work either btw, it throws an exception.
You should look at the implementation in Mike Hadlow's (new BSD) SutekiShop.
In there you will find a DataBindAttribute and BindUsingAttribute which, if I understand correctly, do exactly what you want to do. Notice how the DataBindAttribute.Fetch property is used to rebind the incoming data, or not, (from an HttpPost) to a LINQ entity.
I followed this pattern for one of my projects using ASP.NET MVC and LINQ-To-SQL. It works beautifully.
Here's the source: http://www.google.com/codesearch?hl=en&lr=&q=DataBind+package%3Ahttp%3A%2F%2Fsutekishop.googlecode.com&sbtn=Search
I think the project you pass in to the method is the one you want to perform UpdateModel with isn't it?
Otherwise you are trying to update with pre-existing values not new ones.
Just a thought,
Dan
Code cut out below
[AcceptVerbs (HttpVerbs.Post)]
public ActionResult Edit (Project project)
UpdateModel<Project> (project);
if (!ModelState.IsValid)
return View (project);
var projectService = Factory.GetService<IProjectService> ();
project = projectService.SaveProject (project);
return RedirectToAction ("Details", new { id = project.ProjectId });
}
You need to retrieve the original project as you do then to update it with the properties that have changed in project to update then to submit the update request.
EDIT
Try this code I found:
public static void CloneProperties(this object origin, ref object destination)
{
if (destination == null) throw new ArgumentNullException("destination", "Destination object must first be instantiated.");
foreach (var destinationProperty in destination.GetType().GetProperties())
{
if (origin != null && destinationProperty.CanWrite)
{
origin.GetType().GetProperties().Where(x => x.CanRead && (x.Name == destinationProperty.Name && x.PropertyType == destinationProperty.PropertyType)) .ToList() .ForEach(x => destinationProperty.SetValue(destination, x.GetValue(origin, null), null));
}
}
}

Resources