ASP.NET MVC LINQ-2-SQL as model - how to update? - linq-to-sql

Is it possible to use LINQ2SQL as MVC model and bind? - Since L2S "attachement" problems are really showstopping.
[HttpPost]
public ActionResult Save(ItemCart edCart)
{
using (DataContext DB = new DataContext())
{
DB.Carts.Attach(edCart);
DB.Carts.Context.Refresh(RefreshMode.KeepChanges, edCart);
DB.Carts.Context.SubmitChanges();
DB.SubmitChanges();
}
return RedirectToAction("Index");
}
That does not work. :S

What does your Save View look like?
You can't just attach a new item to the EntitySet like that. -> Attaching requires a lot of checks and it is a real pain to implement. I tried it myself and didn't like it at all.
In your [HttpPost] method you'll need to update the model before you can save it:
[HttpPost]
public ActionResult Save(int id, ItemCart edCart) {
DataContext DB = new DataContext(); // I'm doing this without a using keyword for cleanliness
var originalCart = DB.Carts.SingleOrDefault(c => c.ID == id); // First you need to get the old database entry
if (ModelState.IsValid & TryUpdateModel(edCart, "Cart")) { // This is where the magic happens.
// Save New Instance
DB.SubmitChanges.
return RedirectToAction("Details", new { id = originalCart.ID });
} else {
// Invalid - redisplay with errors
return View(edCart);
}
}
It tries to update the model from the controllers valueprovider using they "Cart" prefix.

Related

MvvmCross IMvxNavigationFacade, MvxViewModelRequest causes Init() to be called rather than Prepare()

I've implemented an IMvxNavigationFacade for deep linking in my MvvmCross 5.6.x sample app. I've added logic in BuildViewModelRequest() to construct a MvxViewModelRequest with parameters passed in as MvxBundle.
if (url.StartsWith("http://www.rseg.net/rewards/"))
{
var parametersBundle = new MvxBundle();
var id = url.Substring(url.LastIndexOf('/') + 1);
parametersBundle.Data.Add("id", id);
return Task.FromResult(
new MvxViewModelRequest(typeof(RewardDetailViewModel),
parametersBundle, null));
}
However, this approach causes the old style Init() method to be called in the target ViewModel rather than the new typesafe Prepare() method.
public class RewardDetailViewModel :
MvxViewModel<RewardDetailViewModel.Parameteres>
{
...
public new void Init(string id)
{
if (!string.IsNullOrWhiteSpace(id))
{
if (int.TryParse(id, out _rewardId))
RaiseAllPropertiesChanged();
}
}
public override void Prepare(Parameteres parameter)
{
if (parameter != null)
{
_rewardId = parameter.RewardId;
RaiseAllPropertiesChanged();
}
}
}
Is there a way to construct a MvxViewModelRequest so that you pass in an instance of the parameter class for the target ViewModel causing the Prepare() method to be called?
The entire solution can be viewed on GitHub https://github.com/rsegtx/So.MvvmNav2
Thanks in advance!
After doing some research I found at lease one way to accomplish this.
Create a ViewModelInstanceRequest rather than a ViewModelRequest so that you can call ViewModelLoader.LoadViewModel passing in a parameters object; the ViewModelRequest only allows parameters to be passed using a MvxBundle. Make the following change to BuildViewModelRequest() on the NavigationFacade:
var request = new
MvxViewModelInstanceRequest(typeof(RewardDetailViewModel));
var parameters = new RewardDetailViewModel.Parameteres();
.... parse parameters and fill in parameters object
request.ViewModelInstance = ViewModelLoader.LoadViewModel(
request, parameters, null);
return Task.FromResult((MvxViewModelRequest)request);
Create your own IMvxNavigationService and add logic to inspect the object returned from the NavigationFacde and if it is a ViewModelInstanceRequest then use it as is rather than one previously creating.
var facadeRequest = await facade.BuildViewModelRequest(path,
paramDict).ConfigureAwait(false);
...
if (facadeRequest is MvxViewModelInstanceRequest)
request = facadeRequest as MvxViewModelInstanceRequest;
else
{
facadeRequest.ViewModelType = facadeRequest.ViewModelType;
if (facadeRequest.ParameterValues != null)
{
request.ParameterValues = facadeRequest.ParameterValues;
}
request.ViewModelInstance = ViewModelLoader.LoadViewModel(
request, null);
}
I've updated the original example on GitHub https://github.com/rsegtx/So.MvvmNav2.

How to get ITemplate from razor engine (IRazorEngineService)

I just updated our RazorEngine reference to version 3.7.5. A bunch of things seems to have changed and became obsolete.
For most things I figured out 'the new way', except for 1 thing: getting an ITemplate instance.
We used to use a TemplateService instance. That had a method Resolve, which returns an ITemplate instance.
The TemplateService was replaced with IRazorEngineService. This doesn't have any method returning an ITemplate.
What's the correct way to retrieve one?
As I already discussed this on some threads here some quotes:
Can you elaborate the reasons why you need access to instances of that interface directly?
I decided to remove direct access to it as it isn't easy to use and mostly doesn't do what you think it does. It also can cause problems in case you use the Isolation API.
https://github.com/Antaris/RazorEngine/issues/225
If its about setting custom layouts the proper upgrade path is to use a custom TemplateBase and make use of the ViewBag (as discussed on the linked issue).
The other more interesting use case is to get data OUT of the template.
This is discussed in detail here: https://github.com/Antaris/RazorEngine/issues/238
Here is a code sample on how to get out the 'Subject' from the given template
Template:
#model HelloWorldModel
#{
Layout = "CI";
Subject = "Hello World";
}
Hello #Model.Name,<br/>
this is a test email...
Code (simplified)
class CustomDataHolder {
public string Destination { get; set; }
public string Subject { get; set; }
}
// In the custom TemplateBase class:
public string Subject { get { return Viewbag.DataHolder.Subject; }; set { Viewbag.DataHolder.Subject = value; } }
// Your code
public static Task SendEmailAsync<T>(string templateName, string destination, T model)
{
var holder = new CustomDataHolder ();
dynamic viewbag = new DynamicViewBag();
viewbag.DataHolder = holder;
holder.Destination= destination;
var body = Engine.Razor.Run(templateName, typeof(T), model, (DynamicViewBag)viewbag);
MailMessage msg = new MailMessage();
msg.To.Add(new MailAddress(holder.Destination));
msg.Subject = holder.Subject;
msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(body, null, MediaTypeNames.Text.Html));
SmtpClient smtpClient = new SmtpClient();
return smtpClient.SendMailAsync(msg);
}
Hope this covers your use case. Otherwise please add more information to your question on what you trying to achieve with the ITemplate instances....

EF6 - There is already an open DataReader associated with this Command which must be closed first

I am coding a MVC5 internet application and am using EF6.
I have an Edit ActionResult that is called when an Asset object is edited. I also need to update other objects values when an Asset object is edited. The UpdateAssociatedAssetObjects function does this.
I am getting the following error:
There is already an open DataReader associated with this Command which must be closed first.
In the UpdateAssociatedAssetObjects function, at the following line of code:
if (item.mapMarker.Id == asset.Id)
Here is the Edit ActionResult:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(AssetViewModel assetViewModel)
{
if (ModelState.IsValid)
{
db.Entry(assetViewModel.asset).State = EntityState.Modified;
assetViewModel.asset.lastUpdate = DateTime.Now;
if (assetViewModel.asset.linkFromExternalResource)
{
assetViewModel.asset.webAddress = assetViewModel.webAddress;
}
else
{
assetViewModel.asset.webAddress = assetViewModel.filename;
}
db.Entry(assetViewModel.asset).Property(uco => uco.creationDate).IsModified = false;
db.Entry(assetViewModel.asset).Property(uco => uco.userName).IsModified = false;
assetService.UpdateAssociatedAssetObjects(db, assetViewModel.asset);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(assetViewModel);
}
Here is the UpdateAssociatedAssetObjects function:
public void UpdateAssociatedAssetObjects(CanFindLocationDatabaseContext db, Asset asset)
{
foreach (var item in db.mapLocations)
{
if (item.mapMarker.Id == asset.Id)
{
item.lastUpdate = DateTime.Now;
}
}
}
Can I please have some help with this code?
I have tried placing the UpdateAssociatedAssetObjects function after the await db.SaveChangesAsync() and using a new database context object, but the error still occurs.
Thanks in advance
In your controller method you are already opening a db connection to an entry of asset.
In your method UpdateAssociatedAssetObjects you're trying to open a second db connection reading, while you're main is still open. Get the first object or get the list in the second object.
An alternate solution is to update the db twice.

Model binding issue in LINQ to SQL

I am just now starting to work with LINQ, and am pretty familiar with MVC. I have a strongly typed view that is updating a record. I have successfully done a creation:
This works fine, and creates a record in the database:
public ActionResult Create(TABLEMODEL tableModel)
{
DBDataContext db = new DBDataContext();
if (ModelState.IsValid)
{
db.TABLEMODEL.InsertOnSubmit(tableModel);
db.SubmitChanges();
}
}
But when trying to update:
public ActionResult Manage(TABLEMODEL tableModel)
{
DBDataContext db = new DBDataContext();
if (ModelState.IsValid)
{
db.SubmitChanges();
}
}
This fails, in the sense that it does not update the record in the database. No actual error/exception occurs, and I can step through it just fine.
I am sure I am missing something, but cannot find what. I appreciate any help on this matter.
UPDATE
I did notice that if I get a record using the DataContext:
DBDataContext db = new DBDataContext();
var m = db.TABLEMODELs.Single(m => m.ID == 1);
m.Name = "UpdatedName";
db.SubmitChanges();
This does update, so I assume I am somehow not binding from my model to the LINQ context.
My Solution
I found that you need to retrieve the object and then update that with the form. Simple enough.
[HttpPost]
public ActionResult Manage(int ID, FormCollection form)
{
DBSDataContext db = new DBSDataContext();
var t= db.TABLEMODELs.Single(b => b.ID == ID);
UpdateModel(t);
if (ModelState.IsValid)
{
db.SubmitChanges();
}
return View(t);
}
You should re-query the original tableModel, map the updated row and then update.
Perhaps something like this (example only, not knowing anything about your schema):
var originalTableModel = db.GetById( tableModel.Id);
originalTableModel.FirstName = tableModel.FirstName;
db.SubmitChanges();

Duplicate entries

I have created a universalrepository that takes the type passed to it and when I create data entry method, the entity is created fine, but when I create a linked entity to it, i get the base entity created again. Any ideas why?
Details..
I have divided a specification into multiple tables to manage stuff...
Now I have got a person entity, an applicant entity...(in reality applicant and person are the same), a contractor entity. A contractor can only be created by an applicant and therefore an applicant will always be created and therefore a person will always be created.
When I go on creating a person, it creates a person fine, but when I create an applicant it creates a person again. Likewise when I create a contractor it creates a person and multiple applicants for some reason.
Here is my LINQ to SQL. If you notice in anyway I can improve this code, I will appreciate that too.
here is the repository
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Linq;
namespace ParkingPermit.Models
{
public class UniversalManagerRepository<T> :IRepositoryT<T>
where T:class
{
private Table<T> _table;
private readonly DB _db ;//= new DB();
public UniversalManagerRepository()
{
_db = new DB();
_table = _db.GetTable<T>();
}
#region IRepositoryT<T> Members
public T Create(T create)
{
// _table = new DB().GetTable<T>();
//_db.GetTable(typeof(T)).InsertOnSubmit(create);
_table.InsertOnSubmit(create);
Save();
return create;
}
public void Delete(T delete)
{
throw new NotImplementedException();
}
public T Edit(T edit)
{
throw new NotImplementedException();
}
public T GetItem(int id)
{
throw new NotImplementedException();
}
public T Update(T update)
{
throw new NotImplementedException();
}
public IEnumerable<T> List()
{
//IQueryable i = _db.GetTable(typeof(T)).AsQueryable() ;
return _db.GetTable(typeof(T)) as IEnumerable<T>;
//throw new NotImplementedException();
}
public void Save()
{
//_db.SubmitChanges();
_table.Context.SubmitChanges();
//throw new NotImplementedException();
}
#endregion
}
}
I can post an image of the linq to sql designer if that helps, but I cant see the feature here...
Many thanksalt text http://img509.imageshack.us/img509/2072/linq.jpg
the thing is that when applicant is added and an applicant.Person is assigned from the session(in model binder), it creates a new person, which is actually the original person created in the beginning. How can I avoid that.
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var personType = (Person)controllerContext.HttpContext.Session[PersonSessionKey];
controllerContext.HttpContext.Session[CurrentApplicantSessionKey] = null;
var av = new ApplicantValidator(new ModelStateWrapper(bindingContext.ModelState));
var newApplicant = bindingContext.Model as Applicant;
if (personType == null)
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName,
"Cannot Update this Instance directly, please restart the application");
// controllerContext.HttpContext.Session[PersonSessionKey] = personType;
}
else if (newApplicant != null)
{
if (newApplicant.Person != null)
{
if (newApplicant.Person.Equals(personType as Person))
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName,
"A person with these details already exists, please restart the application...");
//return
controllerContext.HttpContext.Session[PersonSessionKey] = null;
personType = null;
}
}
else if (av.Validate(newApplicant))
{
if (newApplicant.Person == null)
{
newApplicant.Person = personType as Person;
newApplicant.PersonId = personType.PersonId;
}
}
}
}
I have resolved this part and apparently its now giving issued with update, can anbody find anything unusual.
Answer to my first problem, was that in Model Binders the entity is being manipulated from sessions and the created back to the service layer.
Apparently it seems that because its all happening outside linq orm framework, this entity needs to be recreated as "From clause ...from ..in db." and then linq correctly recognizes it and does the correct job of insertion.
Can anyone help me with the update/edit..please