Asp.net mvc 3 在模型上编辑-复杂类型未正确更新
我有两个对象-杂志和作者(M-M关系): 以下是预编辑/更新的变量- …以下是添加新作者后的变量 我开始怀疑作者实体的表现,编辑后,它没有绑定到任何杂志,我猜这就是为什么它没有被更新回杂志实体-但这是令人困惑的,因为我正在有效地处理相同的杂志实体-我猜这可能与作者的自定义模型活页夹有关 有人能帮上忙吗 为了完整性-我也加入了AuthorModelBinder类-Asp.net mvc 3 在模型上编辑-复杂类型未正确更新,asp.net-mvc-3,entity-framework-4,model-binding,Asp.net Mvc 3,Entity Framework 4,Model Binding,我有两个对象-杂志和作者(M-M关系): 以下是预编辑/更新的变量- …以下是添加新作者后的变量 我开始怀疑作者实体的表现,编辑后,它没有绑定到任何杂志,我猜这就是为什么它没有被更新回杂志实体-但这是令人困惑的,因为我正在有效地处理相同的杂志实体-我猜这可能与作者的自定义模型活页夹有关 有人能帮上忙吗 为了完整性-我也加入了AuthorModelBinder类- public class AuthorModelBinder : IModelBinder { public
public class AuthorModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var values = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (values != null)
{
// We have specified asterisk (*) as a token delimiter. So
// the ids will be separated by *. For example "2*3*5"
var ids = values.AttemptedValue.Split('*');
List<int> validIds = new List<int>();
foreach (string id in ids)
{
int successInt;
if (int.TryParse(id, out successInt))
{
validIds.Add(successInt);
}
else
{
//Make a new author
AUTHOR author = new AUTHOR();
author.FULL_NAME = id.Replace("\'", "").Trim();
using (RefmanEntities db = new RefmanEntities())
{
db.AUTHORs.Add(author);
db.SaveChanges();
validIds.Add((int)author.AUTHOR_ID);
}
}
}
//Now that we have the selected ids we could fetch the corresponding
//authors from our datasource
var authors = AuthorController.GetAllAuthors().Where(x => validIds.Contains((int)x.Key)).Select(x => new AUTHOR
{
AUTHOR_ID = x.Key,
FULL_NAME = x.Value
}).ToList();
return authors;
}
return Enumerable.Empty<AUTHOR>();
}
}
公共类AuthorModelBinder:IModelBinder
{
公共对象绑定模型(ControllerContext ControllerContext,ModelBindingContext bindingContext)
{
var values=bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
如果(值!=null)
{
//我们已经指定星号(*)作为标记分隔符
//ID将以*分隔。例如“2*3*5”
var id=values.AttemptedValue.Split('*');
List validIds=新列表();
foreach(id中的字符串id)
{
int序列;
if(int.TryParse(id,out successInt))
{
有效添加(成功);
}
其他的
{
//改名
作者=新作者();
author.FULL\u NAME=id.Replace(“\”,“).Trim();
使用(RefmanEntities db=new RefmanEntities())
{
db.AUTHORs.Add(author);
db.SaveChanges();
validIds.Add((int)author.author_ID);
}
}
}
//既然我们有了选定的ID,我们就可以获取相应的ID
//来自我们数据源的作者
var authors=AuthorController.GetAllAuthors()。其中(x=>validds.Contains((int)x.Key))。选择(x=>newauthor
{
作者ID=x.密钥,
全名=x.值
}).ToList();
返回作者;
}
返回可枚举的.Empty();
}
}
此行db.Entry(杂志).State=EntityState.Modified代码>仅告知EF刀库实体已更改。它没有提到关系。如果调用Attach
,对象图中的所有实体都将以Unchanged
状态附着,并且必须分别处理它们。在多对多关系(以及关系状态的变化)的情况下,更糟糕的是
我花了很多时间在这上面。一般有三种方法:
- 您将与您的实体一起发送附加信息,以查找已更改的内容和已删除的内容(是的,您还需要跟踪已删除的项目或关系)。然后,您将手动设置对象图中每个实体和关系的状态
- 您将只使用当前拥有的数据,而不是将它们附加到上下文中,您将加载当前杂志和您需要的每个作者,并在这些加载的实体上重建这些更改
- 您根本不会这样做,而是使用轻量级AJAX调用来添加或删除每个作者。我发现这在许多复杂的UI中很常见
此行db.Entry(杂志).State=EntityState.Modified代码>仅告知EF刀库实体已更改。它没有提到关系。如果调用Attach
,对象图中的所有实体都将以Unchanged
状态附着,并且必须分别处理它们。在多对多关系(以及关系状态的变化)的情况下,更糟糕的是
我花了很多时间在这上面。一般有三种方法:
- 您将与您的实体一起发送附加信息,以查找已更改的内容和已删除的内容(是的,您还需要跟踪已删除的项目或关系)。然后,您将手动设置对象图中每个实体和关系的状态
- 您将只使用当前拥有的数据,而不是将它们附加到上下文中,您将加载当前杂志和您需要的每个作者,并在这些加载的实体上重建这些更改
- 您根本不会这样做,而是使用轻量级AJAX调用来添加或删除每个作者。我发现这在许多复杂的UI中很常见
当我使用MVC/Nhibernate开发我的博客时,我遇到了一个非常类似的场景,实体是Post
和Tag
我也有这样的编辑动作
public ActionResult Edit(Post post)
{
if (ModelState.IsValid)
{
repo.EditPost(post);
...
}
...
}
但与您不同的是,我为Post
而不是标签创建了一个自定义模型活页夹。在定制PostModelBinder
中,我所做的与您在那里所做的几乎相同(但我不会像您为Author
s所做的那样创建新的Tag
s)。基本上,我创建了一个新的Post
实例,从发布的表单中填充它的所有属性,并从数据库中获取ID的所有标记。注意,我只从数据库中获取了标记,而不是Post
我可能建议您为杂志创建一个ModelBinder
,并查看它。另外,最好使用存储库模式,而不是直接从控制器进行调用
更新:
以下是Post
model活页夹的完整源代码
namespace PrideParrot.Web.Controllers.ModelBinders
{
[ValidateInput(false)]
public class PostBinder : IModelBinder
{
private IRepository repo;
public PostBinder(IRepository repo)
{
this.repo = repo;
}
#region IModelBinder Members
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
HttpRequestBase request = controllerContext.HttpContext.Request;
// retrieving the posted values.
string oper = request.Form.Get("oper"),
idStr = request.Form.Get("Id"),
heading = request.Form.Get("Heading"),
description = request.Form.Get("Description"),
tagsStr = request.Form.Get("Tags"),
postTypeIdStr = request.Form.Get("PostType"),
postedDateStr = request.Form.Get("PostedDate"),
isPublishedStr = request.Form.Get("Published"),
fileName = request.Form.Get("FileName"),
serialNoStr = request.Form.Get("SerialNo"),
metaTags = request.Form.Get("MetaTags"),
metaDescription = request.Form.Get("MetaDescription"),
themeIdStr = request.Form.Get("Theme");
// initializing to default values.
int id = 0, serialNo = 0;
DateTime postedDate = DateTime.UtcNow;
DateTime? modifiedDate = DateTime.UtcNow;
postedDate.AddMilliseconds(-postedDate.Millisecond);
modifiedDate.Value.AddMilliseconds(-modifiedDate.Value.Millisecond);
/*if operation is not specified throw exception.
operation should be either'add' or 'edit'*/
if (string.IsNullOrEmpty(oper))
throw new Exception("Operation not specified");
// if there is no 'id' in edit operation add error to model.
if (string.IsNullOrEmpty(idStr) || idStr.Equals("_empty"))
{
if (oper.Equals("edit"))
bindingContext.ModelState.AddModelError("Id", "Id is empty");
}
else
id = int.Parse(idStr);
// check if heading is not empty.
if (string.IsNullOrEmpty(heading))
bindingContext.ModelState.AddModelError("Heading", "Heading: Field is required");
else if (heading.Length > 500)
bindingContext.ModelState.AddModelError("HeadingLength", "Heading: Length should not be greater than 500 characters");
// check if description is not empty.
if (string.IsNullOrEmpty(description))
bindingContext.ModelState.AddModelError("Description", "Description: Field is required");
// check if tags is not empty.
if (string.IsNullOrEmpty(metaTags))
bindingContext.ModelState.AddModelError("Tags", "Tags: Field is required");
else if (metaTags.Length > 500)
bindingContext.ModelState.AddModelError("TagsLength", "Tags: Length should not be greater than 500 characters");
// check if metadescription is not empty.
if (string.IsNullOrEmpty(metaTags))
bindingContext.ModelState.AddModelError("MetaDescription", "Meta Description: Field is required");
else if (metaTags.Length > 500)
bindingContext.ModelState.AddModelError("MetaDescription", "Meta Description: Length should not be greater than 500 characters");
// check if file name is not empty.
if (string.IsNullOrEmpty(fileName))
bindingContext.ModelState.AddModelError("FileName", "File Name: Field is required");
else if (fileName.Length > 50)
bindingContext.ModelState.AddModelError("FileNameLength", "FileName: Length should not be greater than 50 characters");
bool isPublished = !string.IsNullOrEmpty(isPublishedStr) ? Convert.ToBoolean(isPublishedStr.ToString()) : false;
//** TAGS
var tags = new List<PostTag>();
var tagIds = tagsStr.Split(',');
foreach (var tagId in tagIds)
{
tags.Add(repo.PostTag(int.Parse(tagId)));
}
if(tags.Count == 0)
bindingContext.ModelState.AddModelError("Tags", "Tags: The Post should have atleast one tag");
// retrieving the post type from repository.
int postTypeId = !string.IsNullOrEmpty(postTypeIdStr) ? int.Parse(postTypeIdStr) : 0;
var postType = repo.PostType(postTypeId);
if (postType == null)
bindingContext.ModelState.AddModelError("PostType", "Post Type is null");
Theme theme = null;
if (!string.IsNullOrEmpty(themeIdStr))
theme = repo.Theme(int.Parse(themeIdStr));
// serial no
if (oper.Equals("edit"))
{
if (string.IsNullOrEmpty(serialNoStr))
bindingContext.ModelState.AddModelError("SerialNo", "Serial No is empty");
else
serialNo = int.Parse(serialNoStr);
}
else
{
serialNo = repo.TotalPosts(false) + 1;
}
// check if commented date is not empty in edit.
if (string.IsNullOrEmpty(postedDateStr))
{
if (oper.Equals("edit"))
bindingContext.ModelState.AddModelError("PostedDate", "Posted Date is empty");
}
else
postedDate = Convert.ToDateTime(postedDateStr.ToString());
// CREATE NEW POST INSTANCE
return new Post
{
Id = id,
Heading = heading,
Description = description,
MetaTags = metaTags,
MetaDescription = metaDescription,
Tags = tags,
PostType = postType,
PostedDate = postedDate,
ModifiedDate = oper.Equals("edit") ? modifiedDate : null,
Published = isPublished,
FileName = fileName,
SerialNo = serialNo,
Theme = theme
};
}
#endregion
}
}
namespace PrideParrot.Web.Controllers.ModelBinders
{
[验证输入(错误)]
公共类PostBinder:IModel
public class AuthorModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var values = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (values != null)
{
// We have specified asterisk (*) as a token delimiter. So
// the ids will be separated by *. For example "2*3*5"
var ids = values.AttemptedValue.Split('*');
List<int> validIds = new List<int>();
foreach (string id in ids)
{
int successInt;
if (int.TryParse(id, out successInt))
{
validIds.Add(successInt);
}
else
{
//Make a new author
AUTHOR author = new AUTHOR();
author.FULL_NAME = id.Replace("\'", "").Trim();
using (RefmanEntities db = new RefmanEntities())
{
db.AUTHORs.Add(author);
db.SaveChanges();
validIds.Add((int)author.AUTHOR_ID);
}
}
}
//Now that we have the selected ids we could fetch the corresponding
//authors from our datasource
var authors = AuthorController.GetAllAuthors().Where(x => validIds.Contains((int)x.Key)).Select(x => new AUTHOR
{
AUTHOR_ID = x.Key,
FULL_NAME = x.Value
}).ToList();
return authors;
}
return Enumerable.Empty<AUTHOR>();
}
}
public ActionResult Edit(Post post)
{
if (ModelState.IsValid)
{
repo.EditPost(post);
...
}
...
}
namespace PrideParrot.Web.Controllers.ModelBinders
{
[ValidateInput(false)]
public class PostBinder : IModelBinder
{
private IRepository repo;
public PostBinder(IRepository repo)
{
this.repo = repo;
}
#region IModelBinder Members
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
HttpRequestBase request = controllerContext.HttpContext.Request;
// retrieving the posted values.
string oper = request.Form.Get("oper"),
idStr = request.Form.Get("Id"),
heading = request.Form.Get("Heading"),
description = request.Form.Get("Description"),
tagsStr = request.Form.Get("Tags"),
postTypeIdStr = request.Form.Get("PostType"),
postedDateStr = request.Form.Get("PostedDate"),
isPublishedStr = request.Form.Get("Published"),
fileName = request.Form.Get("FileName"),
serialNoStr = request.Form.Get("SerialNo"),
metaTags = request.Form.Get("MetaTags"),
metaDescription = request.Form.Get("MetaDescription"),
themeIdStr = request.Form.Get("Theme");
// initializing to default values.
int id = 0, serialNo = 0;
DateTime postedDate = DateTime.UtcNow;
DateTime? modifiedDate = DateTime.UtcNow;
postedDate.AddMilliseconds(-postedDate.Millisecond);
modifiedDate.Value.AddMilliseconds(-modifiedDate.Value.Millisecond);
/*if operation is not specified throw exception.
operation should be either'add' or 'edit'*/
if (string.IsNullOrEmpty(oper))
throw new Exception("Operation not specified");
// if there is no 'id' in edit operation add error to model.
if (string.IsNullOrEmpty(idStr) || idStr.Equals("_empty"))
{
if (oper.Equals("edit"))
bindingContext.ModelState.AddModelError("Id", "Id is empty");
}
else
id = int.Parse(idStr);
// check if heading is not empty.
if (string.IsNullOrEmpty(heading))
bindingContext.ModelState.AddModelError("Heading", "Heading: Field is required");
else if (heading.Length > 500)
bindingContext.ModelState.AddModelError("HeadingLength", "Heading: Length should not be greater than 500 characters");
// check if description is not empty.
if (string.IsNullOrEmpty(description))
bindingContext.ModelState.AddModelError("Description", "Description: Field is required");
// check if tags is not empty.
if (string.IsNullOrEmpty(metaTags))
bindingContext.ModelState.AddModelError("Tags", "Tags: Field is required");
else if (metaTags.Length > 500)
bindingContext.ModelState.AddModelError("TagsLength", "Tags: Length should not be greater than 500 characters");
// check if metadescription is not empty.
if (string.IsNullOrEmpty(metaTags))
bindingContext.ModelState.AddModelError("MetaDescription", "Meta Description: Field is required");
else if (metaTags.Length > 500)
bindingContext.ModelState.AddModelError("MetaDescription", "Meta Description: Length should not be greater than 500 characters");
// check if file name is not empty.
if (string.IsNullOrEmpty(fileName))
bindingContext.ModelState.AddModelError("FileName", "File Name: Field is required");
else if (fileName.Length > 50)
bindingContext.ModelState.AddModelError("FileNameLength", "FileName: Length should not be greater than 50 characters");
bool isPublished = !string.IsNullOrEmpty(isPublishedStr) ? Convert.ToBoolean(isPublishedStr.ToString()) : false;
//** TAGS
var tags = new List<PostTag>();
var tagIds = tagsStr.Split(',');
foreach (var tagId in tagIds)
{
tags.Add(repo.PostTag(int.Parse(tagId)));
}
if(tags.Count == 0)
bindingContext.ModelState.AddModelError("Tags", "Tags: The Post should have atleast one tag");
// retrieving the post type from repository.
int postTypeId = !string.IsNullOrEmpty(postTypeIdStr) ? int.Parse(postTypeIdStr) : 0;
var postType = repo.PostType(postTypeId);
if (postType == null)
bindingContext.ModelState.AddModelError("PostType", "Post Type is null");
Theme theme = null;
if (!string.IsNullOrEmpty(themeIdStr))
theme = repo.Theme(int.Parse(themeIdStr));
// serial no
if (oper.Equals("edit"))
{
if (string.IsNullOrEmpty(serialNoStr))
bindingContext.ModelState.AddModelError("SerialNo", "Serial No is empty");
else
serialNo = int.Parse(serialNoStr);
}
else
{
serialNo = repo.TotalPosts(false) + 1;
}
// check if commented date is not empty in edit.
if (string.IsNullOrEmpty(postedDateStr))
{
if (oper.Equals("edit"))
bindingContext.ModelState.AddModelError("PostedDate", "Posted Date is empty");
}
else
postedDate = Convert.ToDateTime(postedDateStr.ToString());
// CREATE NEW POST INSTANCE
return new Post
{
Id = id,
Heading = heading,
Description = description,
MetaTags = metaTags,
MetaDescription = metaDescription,
Tags = tags,
PostType = postType,
PostedDate = postedDate,
ModifiedDate = oper.Equals("edit") ? modifiedDate : null,
Published = isPublished,
FileName = fileName,
SerialNo = serialNo,
Theme = theme
};
}
#endregion
}
}