How do you persist changes to objects returned from a LINQ query to the database? - asp.net-mvc

So I load up an object like...
MyObject object = new MyObject();
object = objectRepository.getObjectByID(id);
object.lastLoaded = DateTime.Now();
...
//NOW WHAT????
I know there is a .Save() but does that update the current one or is that only used for creating new ones?

You want to use the SubmitChanges() method available on your DataContext.
Quick example:
public void updateLoadDate(int id)
{
using(LinqDataContext db = new LinqDataContext())
{
var myObject = (from object in db.objects
where object.id == id
select object).SingleOrDefault();
myObject.lastloaded = DateTime.Now();
db.SubmitChanges();
}
}

Related

MVC Full Calendar Error [duplicate]

I am trying to do a simple JSON return but I am having issues I have the following below.
public JsonResult GetEventData()
{
var data = Event.Find(x => x.ID != 0);
return Json(data);
}
I get a HTTP 500 with the exception as shown in the title of this question. I also tried
var data = Event.All().ToList()
That gave the same problem.
Is this a bug or my implementation?
It seems that there are circular references in your object hierarchy which is not supported by the JSON serializer. Do you need all the columns? You could pick up only the properties you need in the view:
return Json(new
{
PropertyINeed1 = data.PropertyINeed1,
PropertyINeed2 = data.PropertyINeed2
});
This will make your JSON object lighter and easier to understand. If you have many properties, AutoMapper could be used to automatically map between DTO objects and View objects.
I had the same problem and solved by using Newtonsoft.Json;
var list = JsonConvert.SerializeObject(model,
Formatting.None,
new JsonSerializerSettings() {
ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
});
return Content(list, "application/json");
This actually happens because the complex objects are what makes the resulting json object fails.
And it fails because when the object is mapped it maps the children, which maps their parents, making a circular reference to occur. Json would take infinite time to serialize it, so it prevents the problem with the exception.
Entity Framework mapping also produces the same behavior, and the solution is to discard all unwanted properties.
Just expliciting the final answer, the whole code would be:
public JsonResult getJson()
{
DataContext db = new DataContext ();
return this.Json(
new {
Result = (from obj in db.Things select new {Id = obj.Id, Name = obj.Name})
}
, JsonRequestBehavior.AllowGet
);
}
It could also be the following in case you don't want the objects inside a Result property:
public JsonResult getJson()
{
DataContext db = new DataContext ();
return this.Json(
(from obj in db.Things select new {Id = obj.Id, Name = obj.Name})
, JsonRequestBehavior.AllowGet
);
}
To sum things up, there are 4 solutions to this:
Solution 1: turn off ProxyCreation for the DBContext and restore it in the end.
private DBEntities db = new DBEntities();//dbcontext
public ActionResult Index()
{
bool proxyCreation = db.Configuration.ProxyCreationEnabled;
try
{
//set ProxyCreation to false
db.Configuration.ProxyCreationEnabled = false;
var data = db.Products.ToList();
return Json(data, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(ex.Message);
}
finally
{
//restore ProxyCreation to its original state
db.Configuration.ProxyCreationEnabled = proxyCreation;
}
}
Solution 2: Using JsonConvert by Setting ReferenceLoopHandling to ignore on the serializer settings.
//using using Newtonsoft.Json;
private DBEntities db = new DBEntities();//dbcontext
public ActionResult Index()
{
try
{
var data = db.Products.ToList();
JsonSerializerSettings jss = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
var result = JsonConvert.SerializeObject(data, Formatting.Indented, jss);
return Json(result, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(ex.Message);
}
}
Following two solutions are the same, but using a model is better because it's strong typed.
Solution 3: return a Model which includes the needed properties only.
private DBEntities db = new DBEntities();//dbcontext
public class ProductModel
{
public int Product_ID { get; set;}
public string Product_Name { get; set;}
public double Product_Price { get; set;}
}
public ActionResult Index()
{
try
{
var data = db.Products.Select(p => new ProductModel
{
Product_ID = p.Product_ID,
Product_Name = p.Product_Name,
Product_Price = p.Product_Price
}).ToList();
return Json(data, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(ex.Message);
}
}
Solution 4: return a new dynamic object which includes the needed properties only.
private DBEntities db = new DBEntities();//dbcontext
public ActionResult Index()
{
try
{
var data = db.Products.Select(p => new
{
Product_ID = p.Product_ID,
Product_Name = p.Product_Name,
Product_Price = p.Product_Price
}).ToList();
return Json(data, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(ex.Message);
}
}
JSON, like xml and various other formats, is a tree-based serialization format. It won't love you if you have circular references in your objects, as the "tree" would be:
root B => child A => parent B => child A => parent B => ...
There are often ways of disabling navigation along a certain path; for example, with XmlSerializer you might mark the parent property as XmlIgnore. I don't know if this is possible with the json serializer in question, nor whether DatabaseColumn has suitable markers (very unlikely, as it would need to reference every serialization API)
add [JsonIgnore] to virtuals properties in your model.
Using Newtonsoft.Json: In your Global.asax Application_Start method add this line:
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
Its because of the new DbContext T4 template that is used for generating the EntityFramework entities. In order to be able to perform the change tracking, this templates uses the Proxy pattern, by wrapping your nice POCOs with them. This then causes the issues when serializing with the JavaScriptSerializer.
So then the 2 solutions are:
Either you just serialize and return the properties you need on the client
You may switch off the automatic generation of proxies by setting it on the context's configuration
context.Configuration.ProxyCreationEnabled = false;
Very well explained in the below article.
http://juristr.com/blog/2011/08/javascriptserializer-circular-reference/
Provided answers are good, but I think they can be improved by adding an "architectural" perspective.
Investigation
MVC's Controller.Json function is doing the job, but it is very poor at providing a relevant error in this case. By using Newtonsoft.Json.JsonConvert.SerializeObject, the error specifies exactly what is the property that is triggering the circular reference. This is particularly useful when serializing more complex object hierarchies.
Proper architecture
One should never try to serialize data models (e.g. EF models), as ORM's navigation properties is the road to perdition when it comes to serialization. Data flow should be the following:
Database -> data models -> service models -> JSON string
Service models can be obtained from data models using auto mappers (e.g. Automapper). While this does not guarantee lack of circular references, proper design should do it: service models should contain exactly what the service consumer requires (i.e. the properties).
In those rare cases, when the client requests a hierarchy involving the same object type on different levels, the service can create a linear structure with parent->child relationship (using just identifiers, not references).
Modern applications tend to avoid loading complex data structures at once and service models should be slim. E.g.:
access an event - only header data (identifier, name, date etc.) is loaded -> service model (JSON) containing only header data
managed attendees list - access a popup and lazy load the list -> service model (JSON) containing only the list of attendees
Avoid converting the table object directly. If relations are set between other tables, it might throw this error.
Rather, you can create a model class, assign values to the class object and then serialize it.
I'm Using the fix, Because Using Knockout in MVC5 views.
On action
return Json(ModelHelper.GetJsonModel<Core_User>(viewModel));
function
public static TEntity GetJsonModel<TEntity>(TEntity Entity) where TEntity : class
{
TEntity Entity_ = Activator.CreateInstance(typeof(TEntity)) as TEntity;
foreach (var item in Entity.GetType().GetProperties())
{
if (item.PropertyType.ToString().IndexOf("Generic.ICollection") == -1 && item.PropertyType.ToString().IndexOf("SaymenCore.DAL.") == -1)
item.SetValue(Entity_, Entity.GetPropValue(item.Name));
}
return Entity_;
}
You can notice the properties that cause the circular reference. Then you can do something like:
private Object DeCircular(Object object)
{
// Set properties that cause the circular reference to null
return object
}
//first: Create a class as your view model
public class EventViewModel
{
public int Id{get;set}
public string Property1{get;set;}
public string Property2{get;set;}
}
//then from your method
[HttpGet]
public async Task<ActionResult> GetEvent()
{
var events = await db.Event.Find(x => x.ID != 0);
List<EventViewModel> model = events.Select(event => new EventViewModel(){
Id = event.Id,
Property1 = event.Property1,
Property1 = event.Property2
}).ToList();
return Json(new{ data = model }, JsonRequestBehavior.AllowGet);
}
An easier alternative to solve this problem is to return an string, and format that string to json with JavaScriptSerializer.
public string GetEntityInJson()
{
JavaScriptSerializer j = new JavaScriptSerializer();
var entityList = dataContext.Entitites.Select(x => new { ID = x.ID, AnotherAttribute = x.AnotherAttribute });
return j.Serialize(entityList );
}
It is important the "Select" part, which choose the properties you want in your view. Some object have a reference for the parent. If you do not choose the attributes, the circular reference may appear, if you just take the tables as a whole.
Do not do this:
public string GetEntityInJson()
{
JavaScriptSerializer j = new JavaScriptSerializer();
var entityList = dataContext.Entitites.toList();
return j.Serialize(entityList );
}
Do this instead if you don't want the whole table:
public string GetEntityInJson()
{
JavaScriptSerializer j = new JavaScriptSerializer();
var entityList = dataContext.Entitites.Select(x => new { ID = x.ID, AnotherAttribute = x.AnotherAttribute });
return j.Serialize(entityList );
}
This helps render a view with less data, just with the attributes you need, and makes your web run faster.

Best Way to Update only modified fields with Entity Framework

Currently I am doing like this:
For Example:
public update(Person model)
{
// Here model is model return from form on post
var oldobj = db.Person.where(x=>x.ID = model.ID).SingleOrDefault();
db.Entry(oldobj).CurrentValues.SetValues(model);
}
It works, but for example,
I have 50 columns in my table but I displayed only 25 fields in my form (I need to partially update my table, with remaining 25 column retain same old value)
I know it can be achieve by "mapping columns one by one" or by creating "hidden fields for those remaining 25 columns".
Just wondering is there any elegant way to do this with less effort and optimal performance?
This is a very good question. By default I have found that as long as change tracking is enabled (it is by default unless you turn it off), Entity Framework will do a good job of applying to the database only what you ask it to change.
So if you only change 1 field against the object and then call SaveChanges(), EF will only update that 1 field when you call SaveChanges().
The problem here is that when you map a view model into an entity object, all of the values get overwritten. Here is my way of handling this:
In this example, you have a single entity called Person:
Person
======
Id - int
FirstName - varchar
Surname - varchar
Dob - smalldatetime
Now let's say we want to create a view model which will only update Dob, and leave all other fields exactly how they are, here is how I do that.
First, create a view model:
public class PersonDobVm
{
public int Id { get; set; }
public DateTime Dob { get; set; }
public void MapToModel(Person p)
{
p.Dob = Dob;
}
}
Now write the code roughly as follows (you'll have to alter it to match your context name etc):
DataContext db = new DataContext();
Person p = db.People.FirstOrDefault();
// you would have this posted in, but we are creating it here just for illustration
var vm = new PersonDobVm
{
Id = p.Id, // the Id you want to update
Dob = new DateTime(2015, 1, 1) // the new DOB for that row
};
vm.MapToModel(p);
db.SaveChanges();
The MapToModel method could be even more complicated and do all kinds of additional checks before assigning the view model fields to the entity object.
Anyway, the result when SaveChanges is called is the following SQL:
exec sp_executesql N'UPDATE [dbo].[Person]
SET [Dob] = #0
WHERE ([Id] = #1)
',N'#0 datetime2(7),#1 int',#0='2015-01-01 00:00:00',#1=1
So you can clearly see, Entity Framework has not attempted to update any other fields - just the Dob field.
I know in your example you want to avoid coding each assignment by hand, but I think this is the best way. You tuck it all away in your VM so it does not litter your main code, and this way you can cater for specific needs (i.e. composite types in there, data validation, etc). The other option is to use an AutoMapper, but I do not think they are safe. If you use an AutoMapper and spelt "Dob" as "Doob" in your VM, it would not map "Doob" to "Dob", nor would it tell you about it! It would fail silently, the user would think everything was ok, but the change would not be saved.
Whereas if you spelt "Dob" as "Doob" in your VM, the compiler will alert you that the MapToModel() is referencing "Dob" but you only have a property in your VM called "Doob".
I hope this helps you.
I swear by EntityFramework.Extended. Nuget Link
It lets you write:
db.Person
.Where(x => x.ID == model.ID)
.Update(p => new Person()
{
Name = newName,
EditCount = p.EditCount+1
});
Which is very clearly translated into SQL.
Please try this way
public update(Person model)
{
// Here model is model return from form on post
var oldobj = db.Person.where(x=>x.ID = model.ID).SingleOrDefault();
// Newly Inserted Code
var UpdatedObj = (Person) Entity.CheckUpdateObject(oldobj, model);
db.Entry(oldobj).CurrentValues.SetValues(UpdatedObj);
}
public static object CheckUpdateObject(object originalObj, object updateObj)
{
foreach (var property in updateObj.GetType().GetProperties())
{
if (property.GetValue(updateObj, null) == null)
{
property.SetValue(updateObj,originalObj.GetType().GetProperty(property.Name)
.GetValue(originalObj, null));
}
}
return updateObj;
}
I have solved my Issue by using FormCollection to list out used element in form, and only change those columns in database.
I have provided my code sample below; Great if it can help someone else
// Here
// collection = FormCollection from Post
// model = View Model for Person
var result = db.Person.Where(x => x.ID == model.ID).SingleOrDefault();
if (result != null)
{
List<string> formcollist = new List<string>();
foreach (var key in collection.ToArray<string>())
{
// Here apply your filter code to remove system properties if any
formcollist.Add(key);
}
foreach (var prop in result.GetType().GetProperties())
{
if( formcollist.Contains(prop.Name))
{
prop.SetValue(result, model.GetType().GetProperty(prop.Name).GetValue(model, null));
}
}
db.SaveChanges();
}
I still didn't find a nice solution for my problem, so I created a work around. When loading the Entity, I directly make a copy of it and name it entityInit. When saving the Entity, I compare the both to see, what really was changed. All the unchanged Properties, I set to unchanged and fill them with the Database-Values. This was necessary for my Entities without Tracking:
// load entity without tracking
var entityWithoutTracking = Context.Person.AsNoTracking().FirstOrDefault(x => x.ID == _entity.ID);
var entityInit = CopyEntity(entityWithoutTracking);
// do business logic and change entity
entityWithoutTracking.surname = newValue;
// for saving, find entity in context
var entity = Context.Person.FirstOrDefault(x => x.ID == _entity.ID);
var entry = Context.Entry(entity);
entry.CurrentValues.SetValues(entityWithoutTracking);
entry.State = EntityState.Modified;
// get List of all changed properties (in my case these are all existing properties, including those which shouldn't have changed)
var changedPropertiesList = entry.CurrentValues.PropertyNames.Where(x => entry.Property(x).IsModified).ToList();
foreach (var checkProperty in changedPropertiesList)
{
try
{
var p1 = entityWithoutTracking.GetType().GetProperty(checkProperty).GetValue(entityWithoutTracking);
var p2 = entityInit.GetType().GetProperty(checkProperty).GetValue(entityInit);
if ((p1 == null && p2 == null) || p1.Equals(p2))
{
entry.Property(checkProperty).CurrentValue = entry.Property(checkProperty).OriginalValue; // restore DB-Value
entry.Property(checkProperty).IsModified = false; // throws Exception for Primary Keys
}
} catch(Exception) { }
}
Context.SaveChanges(); // only surname will be updated
This is way I did it, assuming the new object has more columns to update that the one we want to keep.
if (theClass.ClassId == 0)
{
theClass.CreatedOn = DateTime.Now;
context.theClasses.Add(theClass);
}
else {
var currentClass = context.theClasses.Where(c => c.ClassId == theClass.ClassId)
.Select(c => new TheClasses {
CreatedOn = c.CreatedOn
// Add here others fields you want to keep as the original record
}).FirstOrDefault();
theClass.CreatedOn = currentClass.CreatedOn;
// The new class will replace the current, all fields
context.theClasses.Add(theClass);
context.Entry(theClass).State = EntityState.Modified;
}
context.SaveChanges();
In EF you can do like this
var result = db.Person.Where(x => x.ID == model.ID).FirstOrDefault();
if(result != null){
result.Name = newName;
result.DOB = newDOB;
db.Person.Update(result);
}
Or you can use
using (var db= new MyDbContext())
{
var result= db.Person.Where(x => x.ID == model.ID).FirstOrDefault();
result.Name= newName;
result.DOB = newDOB;
db.Update(result);
db.SaveChanges();
}
For more detail please EntityFramework Core - Update Only One Field
No Worry guys
Just write raw sql query
db.Database.ExecuteSqlCommand("Update Person set Name='"+_entity.Name+"' where Id = " + _entity.ID + "");

How do I make EF EntityType.GetProperty Eager load?

I am using the following code snippet to save a modified entity in my repository
object id1 = item.GetProperty("Id");
T originalEntity = dbSet.Find(id1); // but this doesnt update navigation properties
((DbContext)context).Entry(originalEntity).CurrentValues.SetValues(item);
navProps = GetNavigationProperties(originalEntity);
foreach (PropertyInfo navProp in navProps)
{
//Set originalEntity prop value to modifiedEntity value
var newval = (LoggedEntity)navProp.GetValue(item);
object entity = null;
if (newval != null)
{
Type tp = navProp.PropertyType;
DbSet entities = ((DbContext)context).Set(tp);
entity = entities.Find(newval.Id);
}
navProp.SetValue(originalEntity, entity);
}
which calls
public List<PropertyInfo> GetNavigationProperties(T entity)
{
Type t = entity.GetType();
ObjectContext objectContex = ((IObjectContextAdapter)((DbContext)context)).ObjectContext;
EntityType elementType = objectContex.CreateObjectSet<T>().EntitySet.ElementType;
var properties = new List<PropertyInfo>();
Type entityType = entity.GetType();
foreach (NavigationProperty navigationProperty in elementType.NavigationProperties)
{
PropertyInfo prop = entityType.GetProperty(navigationProperty.Name);
properties.Add(prop);
}
return properties;
}
However I have a problem when I want to set the navigation property to null.
The changes simply don't save.
The answer is explained in this question
Which points out that the navigation properties need to be eager loaded.
How do I modify my GetNavigationProperties procedure to eager load?
I inserted the following line
originalEntity.GetProperty(navProp.Name);
before
navProp.SetValue(originalEntity, entity);
and now the code works if entity is null

Enity Framework 4.1 - One trip database update

Let's say I have this code:
class Score
{
public Update(int score)
{
update score but do not call (context.SaveChanges())
}
}
class Foo
{
public DoSomething(int update)
{
Score score = new Score();
score.Update(2);
SomeObj obj = (select object);
obj.Soo = 3;
context.SaveChanges();
}
}
Basically to make it work, I need to explicity provide SaveChanges in method Update. But when I have 4 such methods in row, and 34243 users want to update data, I don't think saving for each one in 4 trips would be a good idea.
Is there way in EF4.1 to delay database update the last moment, in provided example, Or I'm forced to explicity save for each method ?
EDIT:
For clarification. I tried to do not call SaveChanges in external method, and only one time where the changes mu be saved.
I will give an real example:
public class ScoreService : IScoreService
{
private JamiContext _ctx;
private IRepository<User> _usrRepo;
public ScoreService(IRepository<User> usrRepo)
{
_ctx = new JamiContext();
_usrRepo = usrRepo;
}
public void PostScore(int userId, GlobalSettings gs, string name)
{
User user = _ctx.UserSet.Where(x => x.Id == userId).FirstOrDefault();
if (name == "up")
{
user.Rating = user.Rating + gs.ScoreForLike;
}
else if (name == "down")
{
user.Rating = user.Rating - Math.Abs(gs.ScoreForDislike);
}
}
}
And Now:
public PostRating LikeDislike(User user, int postId, int userId, GlobalSettings set, string name)
{
PostRating model = new PostRating();
var post = (from p in _ctx.PostSet
where p.Id == postId
select p).FirstOrDefault();
if (name == "up")
{
post.Like = post.Like + 1;
model.Rating = post.Like - post.Dislike;
}
else if (name == "down")
{
post.Dislike = post.Dislike + 1;
model.Rating = post.Like - post.Dislike;
}
PostVote pv = new PostVote();
pv.PostId = post.Id;
pv.UserId = user.Id;
_ctx.PostVoteSet.Add(pv);
_scoreSrv.PostScore(userId, set, name);
_ctx.SaveChanges();
return model;
}
I this case user rating do not update, Until I call SaveChanges in PostScore
In your example it looks like PostScore and LikeDislike use different context instances. That is the source of your problem and there is no way to avoid calling multiple SaveChanges in that case. The whole operation is single unit of work and because of that it should use single context instance. Using multiple context instances in this case is wrong design.
Anyway even if you call single SaveChanges you will still have separate roundtrip to the database for each updated, inserted or deleted entity because EF doesn't support command batching.
The way to delay database update to the last moment is by not calling SaveChanges until the last moment.
You have complete control over this code, and if your code is calling SaveChanges after every update, then that needs changing.
This not really solves my entire problem, but at least I can use single instance of Context:
With Ninject:
Bind<JamiContext>().To<JamiContext>().InRequestScope();
And then constructor:
private JamiContext _ctx;
private IRepository<User> _usrRepo;
public ScoreService(IRepository<User> usrRepo, JamiContext ctx)
{
_ctx = ctx;
_usrRepo = usrRepo;
}

Entity Framework 4 and Repository Pattern problem

I am having trouble understanding if I am doing this correctly or not. I have 3 entities that are dependent on each other. I am trying to add new objects to these entities and then call save changes ultimately adding the corresponding records to the tables honoring the FK constraints.
I am getting the error:
The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.
In my code I am parsing some XML with linq while adding the new objects to the context as I go. In my service layer I have the following method to handle processing the incoming data.
public void ProcessSurvey(int surveyContentId, int caseNo, string surveyTitle, string reportVersion, string reportXml)
{
// get surveyid
var surveyContent = _surveyContentRepository.GetSurveyContent(surveyContentId);
// create response obj
var surveyResponse = new SurveyResponse()
{
SurveyId = surveyContent.SurveyId,
CaseNo = caseNo,
SurveyTitle = surveyTitle,
ReportVersion = reportVersion,
Created = DateTime.Now,
ResponseXML = reportXml
};
// add response obj to context?
_surveyResponseRepository.Add(surveyResponse);
// get the questions elements from the xml data
var questions = SurveyResponseHelper.GetResponseQuestions(reportXml);
// iterate over questions
foreach (XElement question in questions)
{
SurveyQuestion thisSurveyQuestion = SurveyResponseHelper.ProcSurveyQuestion(question, surveyContentId);
// add question?
_surveyQuestionRepository.Add(thisSurveyQuestion);
// get question answer
SurveyAnswer thisSurveyAnswer = SurveyResponseHelper.GetAnswer(question);
//update the answer with the question and response obj to satisfy the FK reference
thisSurveyAnswer.SurveyQuestion = thisSurveyQuestion;
thisSurveyAnswer.SurveyResponse = surveyResponse; // This is where it breaks ERRROR: The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects
_surveyAnswerRepository.Add(thisSurveyAnswer);
}
//commit
_surveyAnswerRepository.Save();
}
My Repositories look like this..
public interface ISurveyAnswerRepository
{
void Add(SurveyAnswer surveyAnswer);
void Save();
}
public class SurveyAnswerRepository : Repository, ISurveyAnswerRepository
{
//private DiversionProgramsEntities _db;
public SurveyAnswerRepository()
{
//_db = new DiversionProgramsEntities();
}
public void Add(SurveyAnswer surveyAnswer)
{
this.DataContext.SurveyAnswers.AddObject(surveyAnswer);
}
public void Save()
{
this.DataContext.SaveChanges();
}
my base repository
public class Repository
{
private DiversionProgramsEntities _dataContext;
public DiversionProgramsEntities DataContext
{
get { return _dataContext ?? (_dataContext = DatabaseFactory.CreateContext()); }
}
}
and static class / method to create the context
public static class DatabaseFactory
{
public static DiversionProgramsEntities CreateContext()
{
return new DiversionProgramsEntities();
}
}
here is my helper code..
public class SurveyResponseHelper
{
public static IEnumerable<XElement> GetResponseQuestions(string xmlResponseData)
{
XElement xmlData = XElement.Parse(xmlResponseData);
var questions = from n in xmlData.Descendants()
where n.Parent.Name.LocalName == "questions"
select n;
return questions;
}
public static SurveyQuestion ProcSurveyQuestion(XElement question, int surveyContentId)
{
// get the question type
var questionType = question.Name.LocalName;
// get question element text. This is the actual question text
var questionText = question.Elements().Where(e => e.Name.LocalName == "direction").SingleOrDefault().Value;
// check to see if this question exists in the data table, if it does then we will use the questionid from that which will get used to tie the SurveyAnswer to this question.
// if question does not already exist then a new one will be created
SurveyQuestionRepository surveyQuestionRepository = new SurveyQuestionRepository();
SurveyQuestion surveyQuestion;
surveyQuestion = surveyQuestionRepository.GetSurveyQuestion(surveyContentId, questionType, questionText);
if (surveyQuestion == null)
{
surveyQuestion = new SurveyQuestion()
{
QuestionText = questionText,
QuestionType = questionType,
SurveyContentId = surveyContentId
};
}
return surveyQuestion;
}
public static SurveyAnswer GetAnswer(XElement question)
{
// get the answer index value
var answers = question.Elements().Where(e => e.Name.LocalName == "answers").SingleOrDefault();
int userAnswerIndex = Int32.Parse(answers.Attribute("userAnswerIndex").Value);
// move the answers to an array so we can use the index to get the correct answer
XElement[] answersArray = answers.Elements().ToArray();
SurveyAnswer answer = new SurveyAnswer()
{
AnswerText = answersArray[userAnswerIndex].Value
};
return answer;
}
}
It looks like the error is describing perfectly what is going on. In the following line:
var questions = SurveyResponseHelper.GetResponseQuestions(reportXml);
You are getting a question from another class. That class probably creates it's own object context.
You can't attach a question to the answer if they are from different object contexts.
To solve this, the easiest way is to add a parameter to your methods GetResponseQuestions for the datacontext, so your other method can use that the repositories datacontext to get the questions.
Also, various IoC methods would simplify this.
Where does your _surveyContentRepository come from? If it's static I could see a scenario where that holds on to a SurveyContent object which is attached to one DiversionProgramsEntities, and your ProcSurveyQuestion() method finds and returns an existing SurveyQuestion, attached to a different DiversionProgramsEntities.
Other than that, I think a general pointer I can give you is to assign objects to each other using the objects themselves rather than the object Ids, so instead of:
var surveyResponse = new SurveyResponse { SurveyId = surveyContent.SurveyId }
...use:
var surveyResponse = new SurveyResponse { Survey = surveyContent }
This automatically adds your new SurveyResponse object to the same object context to which the SurveyContent object belongs, and means you don't have to manually add anything to a repository. You can assemble your entire object graph like this, then call Save() on the repository you used to retrieve the first object to save the whole thing.
As #TimHoolihan stated the issue is that you are not using the same Data Context for accessing the Survey Responses and Survey Questions and actually I believe the issue lines in the line below from the ProcSurveyQuestion method.
SurveyQuestionRepository surveyQuestionRepository = new SurveyQuestionRepository();
I see that you have a singleton DataContext in the DiversionProgramsEntities class, but I cannot infer from your code if the SurveyQuestionRepository and SurveryResponseRepositories are also using that same context. Based on the error you are getting, I am guessing that they are using separate contexts, so again as #TimHoolihan suggested, you need to modify your code to use the same context for both.
You should also look into the UnitOfWork pattern as this is what you are trying to accomplish here, but you do not have a common context to track all of your changes across.

Resources