Linq to SQL Update - NullReferenceException on update following Detach - linq

I can't currently understand why my update method in my repository for the style entity is throwing a NullReferenceException in my Linq to SQL domain model layer. The problem seems to be that the EntitySet<Product> Product property on the Style object that I am updating is null following the necessary use of a Detach method. The actual exception is thrown on the attach prior to the submission of changes.
My update is only meant to target the Style table, the Product reference should not really come into it, but the relationship is mapped in the code generated by SQLMetal, which is an important part of my application design.
If I delete the foreign key relationship, recreate the mapping class and adjust code accordingly, the problem goes away. I would however like to kep the forign key relationship if possible.
I will attempt to best explain the set up:
The relationship in the SQL server database is like so, with the Product table referencing the Style table:
The entity mapping class is created with SQLMetal, as far as I know, there is nothing unusual in the way this is generated.
The update method is:
public Style Update(Style style)
{
using (var dc = new ProjectERPDataContext(Config.ConnectionStringERPDB))
{
style.Detach();
dc.Style.Attach(style);
dc.Refresh(RefreshMode.KeepCurrentValues, style);
dc.SubmitChanges();
return style;
}
}
The detach method is kept in a partial class:
The detach method exists because without, the exception is thrown: "An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported."
public partial class Style
{
public void DetachEntityRefs()
{
this._Product = default(EntitySet<Product>);
}
}
The entity class starts like:
[Table(Name="dbo.Style")]
public partial class Style : INotifyPropertyChanging, INotifyPropertyChanged
{
private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);
private int _ID;
private string _Name;
private EntitySet<Product> _Product;
So you can see exactly where the exception is thrown:
Full stack trace:
System.NullReferenceException : Object reference not set to an instance of an object.
at System.Data.Linq.Mapping.EntitySetDefSourceAccessor`2.GetValue(T instance)
at System.Data.Linq.Mapping.MetaAccessor`2.GetBoxedValue(Object instance)
at System.Data.Linq.ChangeTracker.StandardChangeTracker.StandardTrackedObject.HasDeferredLoader(MetaDataMember deferredMember)
at System.Data.Linq.ChangeTracker.StandardChangeTracker.StandardTrackedObject.get_HasDeferredLoaders()
at System.Data.Linq.ChangeTracker.StandardChangeTracker.Track(MetaType mt, Object obj, Dictionary`2 visited, Boolean recurse, Int32 level)
at System.Data.Linq.ChangeTracker.StandardChangeTracker.Track(Object obj, Boolean recurse)
at System.Data.Linq.Table`1.Attach(TEntity entity, Boolean asModified)
at ProjectERP.DomainModel.Repositories.SQLStyleRepo.Update(Style style) in SQLStyleRepo.cs: line 45
at ProjectERP.UnitTests.DomainModel.SQLStyleRepoTests.Test_can_update_style() in SQLStyleRepoTests.cs: line 67

I bet the cause is this timebomb you planted here:
this._Product = default(EntitySet<Product>);
default(EntitySet<Product>) is null. Try setting it to something:
this._Product = new EntitySet<Product>();

Related

NHibernate: How to know if, on Flush() SQL will be sent?

I'm a bit puzzled with the NHibernate's IsDirty() method.
Directly after getting a (very large) complex object from my database, NHibernate's ISession.IsDirty() gives 'true'.
IFacadeDAL fd = new FacadeDAL();
// Session's not dirty
IProject proj = fd.GetByID<IProject, string>("123611-3640");
// Session is dirty
However, if i call Commit() like so:
using (ITransaction trans = Facade.Session.Transaction)
{
trans.Begin();
Facade.Session.Save(entity);
trans.Commit();
return true;
}
this results in no sql (exept for "exec sp_reset_connection").
I have read that due to 'mapping-choices' you can get "ghosts" in your session (causing the session to say it's dirty), but wouldn't it then also try to update something? Also, if this is caused e.g. by "converting" an sql bit to a c# bool i don't think i can change it... (no clue if that could be a cause for ghosts, though).
Update 2:
There are several (sql server) views and tables involved here. This is the (very) simplified class:
public class Project : IProject
{
private string id;
private List<IPlantItem> plantItems;
public Project() { }
public virtual string ID
{
get { return id; }
}
public virtual IEnumerable<IPlantItem> PlantItems
{
get { return plantItems; }
}
}
'PlantItem is being stored in a table. So i expect when i change anything in a PlantItem, IsDirty should change to 'true'.
My question is: is there a way to check if the session at that point, on flush() (or in my case on commit() for that matter) would generated actual sql statements? And if not: is there another way of (manually) storing some sort of a snapshot of the session to compare the current session to?
Update 1: I should really also mention these aspects:
that my FlushMode is set to 'None'.
that the underlying data of 'IProject'-object itself is based on a sql-view and therefore has most properties in the mapping set to update="false"
that when i actually change something in an object and use the same method for saving, sql update statements are being sent (and thus all is committed just fine)
In my experience Ghosts can be caused by the database being a nullable int and the mapping an ordinary int.
When the entity gets hydrated the nullable db int is converted to zero and hence it is now dirty.
Another way to get dirty records is by specifying a wrong type in the XML mapping, e.g.
public enum Sex
{
Unspecified,
Male,
Female
}
...
public virtual Sex Sex { get; set; }
and specify an int in the mapping.
<property name="Sex" type="int"/>
See this link to test your mappings which explains in more details.
If some of your entities is dirty - and therefore the ISession is dirty - they you have a mismatch between the properties and the database. For example, imagine you have a column in a table that is nullable, but in your code it is set as not null (an int, for example). NHibernate will consider it dirty, because its current value (0 in case of an integer) is different from the value that came from the db (null). Look for "Ghost properties NHibernate" in Google.

Set value in model when dynamically creating object of model

I have a bunch of models that may or may not have a property "CommonProperty". I want to set that property when I am creating a new object of a selected model. What I have so far, which works is:
ModuleItem model = db.ModuleItems.Find(ModuleItemID);
object o = GetModuleType(model.ControllerName);
private object GetModuleType(string ModelName)
{
string projectName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
Type classtype = Type.GetType(string.Format("{0}.Models.{1}", projectName, ModelName));
PropertyInfo[] properties = classtype.GetProperties();
var classObject = classtype.GetConstructor(new Type[] { }).Invoke(null);
return classObject;
}
What I want to achieve is to set CommonProperty:
o.CommonProperty = DistinctValue;
I tried creating a class that all of my models inherit from with a virtual method and each model then has an override method. Because its not static I can't call it directly and if I create a new ModelBase then when calling the method it doesn't get overriden by the type that object "o" is. I looked at creating an interface but 1 I don't even know how these work, and 2 (probably because of 1) I am not even able to create a way of doing this without build errors.
When stepping through the code I can see all the properties of "o" using the quickwatch or intellisense or whatever it's called. Clearly it is being recognised as the correct type. I can't seem to be able to call the method though because it's not a recognised (or set) type during build only during runtime and therefore I can't build the solution.
What I would do is create a base model (eg: BaseModel) for viewmodels that have the property CommonProperty then check whether the model is of type BaseModel then set the property
if (obj is BaseModel)
{
obj.CommonProperty = DistinctValue;
}
As it turns out I was way overthinking this. Typically the answer is ridiculously simple. No base model necessary, no overrides, no virtuals, no instances, dump it in a try catch and just set the value of the context object.
object o = GetModuleType(model.ControllerName);
db.Entry(o).State = EntityState.Added;
try
{
db.Entry(o).CurrentValues["CommonProperty"] = DistinctValue;
}
catch (Exception err) { }
I did have to make sure I set the state BEFORE setting a current value otherwise it wasn't in the context (or something like that).

Entity Framework Code-First: "The ObjectStateManager cannot track multiple objects with the same key."

I'm running into an issue with Entity Framework code-first in MVC3. I'm hitting this exception:
An object with the same key already exists in the ObjectStateManager.
The ObjectStateManager cannot track multiple objects with the same
key.
This is addressed many times on SO, but I'm having trouble utilizing any of the suggested solutions in my situation.
Here is a code sample:
FestORM.SaleMethod method = new FestORM.SaleMethod
{
Id = 2,
Name = "Test Sale Method"
};
FestContext context = new FestContext();
//everything works without this line:
string thisQueryWillMessThingsUp =
context.SaleMethods.Where(m => m.Id == 2).Single().Name;
context.Entry(method).State = System.Data.EntityState.Modified;
context.SaveChanges();
EDITED to clarify: I am attempting to update an object that already exists in the database.
Everything works fine without the query noted in the code. In my application, my controller is instantiating the context, and that same context is passed to several repositories that are used by the controller--so I am not able to simply use a different context for the initial query operation. I've tried to remove the entity from being tracked in the ObjectStateManager, but I can't seem to get anywhere with that either. I'm trying to figure out a solution that will work for both conditions: sometimes I will be updating an object that is tracked by the ObjectStateManager, and sometimes it will happen to have not been tracked yet.
FWIW, my real repository functions look like this, just like the code above:
public void Update(T entity)
{
//works ONLY when entity is not tracked by ObjectStateManager
_context.Entry(entity).State = System.Data.EntityState.Modified;
}
public void SaveChanges()
{
_context.SaveChanges();
}
Any ideas? I've been fighting this for too long...
The problem is that this query
string thisQueryWillMessThingsUp =
context.SaleMethods.Where(m => m.Id == 2).Single().Name;
brings one instance of the SaleMethod entity into the context and then this code
context.Entry(method).State = System.Data.EntityState.Modified;
attaches a different instance to the context. Both instances have the same primary key, so EF thinks that you are trying to attach two different entities with the same key to the context. It doesn't know that they are both supposed to be the same entity.
If for some reason you just need to query for the name, but don't want to actually bring the full entity into the context, then you can do this:
string thisQueryWillMessThingsUp =
context.SaleMethods.Where(m => m.Id == 2).AsNoTracking().Single().Name;
If what you are tying to do is update an existing entity and you have values for all mapped properties of that entity, then the simplest thing to do is to not run the query and just use:
context.Entry(method).State = System.Data.EntityState.Modified;
If you don't want to update all properties, possibly because you don't have values for all properties, then querying for the entity and setting properties on it before calling SaveChanges is an acceptable approach. There are several ways to do this depending on your exact requirements. One way is to use the Property method, something like so:
var salesMethod = context.SaleMethods.Find(2); // Basically equivalent to your query
context.Entry(salesMethod).Property(e => e.Name).CurrentValue = newName;
context.Entry(salesMethod).Property(e => e.SomeOtherProp).CurrentValue = newOtherValue;
context.SaveChanges();
These blog posts contain some additional information that might be helpful:
http://blogs.msdn.com/b/adonet/archive/2011/01/29/using-dbcontext-in-ef-feature-ctp5-part-4-add-attach-and-entity-states.aspx
http://blogs.msdn.com/b/adonet/archive/2011/01/30/using-dbcontext-in-ef-feature-ctp5-part-5-working-with-property-values.aspx
The obvious answer would be that your not actually saving the method object to the database before you call:
//everything works without this line:
string thisQueryWillMessThingsUp = context.SaleMethods.Where(m => m.Id == 2).Single().Name;
However, I think perhaps this is just a bit a code you left out.
What if you make your entities inherit from an abstract class ie.
public abstract class BaseClass
{
public int Id { get; set; }
}
Then update your Repository to
public class Repository<T> where T : BaseClass
{
.....
public void Update(T entity)
{
_context.Entry(entity).State = entity.Id == 0 ? System.Data.EntityState.Added : System.Data.EntityState.Modified;
}
}
Also you might want to not set the ID of your SaleMethod and let it be generated by the database. Problem could also be because SaleMethod Object in the database has Id of 2 and then you try to add another SaleMethod object with Id 2.
The error you see stems from trying to add another SaleMethod object with ID of 2 to the ObjectStateManager.

Linq InsertOnSubmit is throwing exception: Object reference not set to an instance of an object

The following throws exception.
The course object that is being passed to InsertOnSubmit is of type Course that is generated by Linq.
public ActionResult Course(CourseViewModel c)
{
Course course = (Course) c; //CourseViewModel is derrived from Course
SchedulerDataContext db = new SchedulerDataContext();
db.Courses.InsertOnSubmit(course); // <- this is where exception is thrown
db.SubmitChanges();
}
There are already questions about this here and here, however, I don't understand their answer. Supposedly I'm not creating an object in time. Which object and what exactly needs to happen?
You need to create the Course object before you try to insert it.
Course course = new Course { ... set the properties .. };
SchedulerDataContext db = new SchedulerDataContext();
db.Courses.InsertOnSubmit(course);
db.SubmitChanges();

How do I delete records from a child collection in LINQ to SQL?

I have two tables in my database connected by foreign keys: Page (PageId, other data) and PageTag (PageId, Tag). I've used LINQ to generate classes for these tables, with the page as the parent and the Tag as the child collection (one to many relationship). Is there any way to mark PageTag records for deletion from the database from within the Page class?
Quick Clearification:
I want the child objects to be deleted when the parent DataContext calls SubmitChanges(), not before. I want TagString to behave exactly like any of the other properties of the Page object.
I would like to enable code like the following:
Page page = mDataContext.Pages.Where(page => page.pageId = 1);
page.TagString = "new set of tags";
//Changes have not been written to the database at this point.
mDataContext.SubmitChanges();
//All changes should now be saved to the database.
Here is my situation in detail:
In order to make working with the collection of tags easier, I've added a property to the Page object that treats the Tag collection as a string:
public string TagString {
get {
StringBuilder output = new StringBuilder();
foreach (PageTag tag in PageTags) {
output.Append(tag.Tag + " ");
}
if (output.Length > 0) {
output.Remove(output.Length - 1, 1);
}
return output.ToString();
}
set {
string[] tags = value.Split(' ');
PageTags.Clear();
foreach (string tag in tags) {
PageTag pageTag = new PageTag();
pageTag.Tag = tag;
PageTags.Add(pageTag);
}
}
}
Basically, the idea is that when a string of tags is sent to this property, the current tags of the object are deleted and a new set is generated in their place.
The problem I'm encountering is that this line:
PageTags.Clear();
Doesn't actually delete the old tags from the database when changes are submitted.
Looking around, the "proper" way to delete things seems to be to call the DeleteOnSubmit method of the data context class. But I don't appear to have access to the DataContext class from within the Page class.
Does anyone know of a way to mark the child elements for deletion from the database from within the Page class?
After some more research, I believe I've managed to find a solution. Marking an object for deletion when it's removed from a collection is controlled by the DeleteOnNull parameter of the Association attribute.
This parameter is set to true when the relationship between two tables is marked with OnDelete Cascade.
Unfortunately, there is no way to set this attribute from within the designer, and no way to set it from within the partial class in the *DataContext.cs file. The only way to set it without enabling cascading deletes is to manually edit the *DataContext.designer.cs file.
In my case, this meant finding the Page association, and adding the DeleteOnNull property:
[Association(Name="Page_PageTag", Storage="_Page", ThisKey="PageId", OtherKey="iPageId", IsForeignKey=true)]
public Page Page
{
...
}
And adding the DeleteOnNull attribute:
[Association(Name="Page_PageTag", Storage="_Page", ThisKey="PageId", OtherKey="iPageId", IsForeignKey=true, DeleteOnNull = true)]
public Page Page
{
...
}
Note that the attribute needed to be added to the Page property of the PageTag class, not the other way around.
See also:
Beth Massi -- LINQ to SQL and One-To-Many Relationships
Dave Brace -- LINQ to SQL: DeleteOnNull
Sorry, my bad. That won't work.
It really looks like you need to be doing this in your repository, rather than in your Page class. There, you have access to your original data context.
There is a way to "attach" the original data context, but by the time you do that, it has become quite the code smell.
Do you have a relationship, in your Linq to SQL entity diagram, linking the Page and PageTags tables? If you don't, that is why you can't see the PageTags class from the Page class.
If the foreign key in the PageTags database table is set to Allow Nulls, Linq to SQL will not create the link when you drag the tables into the designer, even if you created a relationship on the SQL Server.
This is one of those areas where OR mapping can get kind of hairy. Providing this TagString property makes things a bit more convenient, but in the long run it obfuscates what is really happening when someone utilizes the TagString property. By hiding the fact that your performing data modification, someone can very easily come along and set the TagString without using your Page entity within the scope of a DataContext, which could lead to some difficult to find bugs.
A better solution would be to add a Tags property on the Page class with the L2S model designer, and require that the PageTags be edited directly on the Tags property, within the scope of a DataContext. Make the TagString property read only, so it can be genreated (and still provide some convenience), but eliminate the confusion and difficulty around setting that property. This kind of change clarifies intent, and makes it obvious what is happening and what is required by consumers of the Page object to make it happen.
Since Tags is a property of your Page object, as long as it is attached to a DataContext, any changes to that collection will properly trigger deletions or insertions in the database in response to Remove or Add calls.
Aaron,
Apparently you have to loop thru your PageTag records, calling DeleteOnSubmit for each one. Linq to SQL should create an aggregate query to delete all of the records at once when you call SubmitChanges, so overhead should be minimal.
replace
PageTags.Clear();
with
foreach (PageTag tag in PageTags)
myDataContext.DeleteOnSubmit(tag);
Aaron:
Add a DataContext member to your PageTag partial class.
partial class PageTag
{
DataClassesDataContext myDataContext = new DataClassesDataContext();
public string TagString {
..etc.
Larger code sample posted at Robert Harvey's request:
DataContext.cs file:
namespace MyProject.Library.Model
{
using Tome.Library.Parsing;
using System.Text;
partial class Page
{
//Part of Robert Harvey's proposed solution.
MyDataContext mDataContext = new TomeDataContext();
public string TagString {
get {
StringBuilder output = new StringBuilder();
foreach (PageTag tag in PageTags) {
output.Append(tag.Tag + " ");
}
if (output.Length > 0) {
output.Remove(output.Length - 1, 1);
}
return output.ToString();
}
set {
string[] tags = value.Split(' ');
//Original code, fails to mark for deletion.
//PageTags.Clear();
//Robert Harvey's suggestion, thorws exception "Cannot remove an entity that has not been attached."
foreach (PageTag tag in PageTags) {
mDataContext.PageTags.DeleteOnSubmit(tag);
}
foreach (string tag in tags) {
PageTag PageTag = new PageTag();
PageTag.Tag = tag;
PageTags.Add(PageTag);
}
}
}
private bool mIsNew;
public bool IsNew {
get {
return mIsNew;
}
}
partial void OnCreated() {
mIsNew = true;
}
partial void OnLoaded() {
mIsNew = false;
}
}
}
Repository Methods:
public void Save() {
mDataContext.SubmitChanges();
}
public Page GetPage(string pageName) {
Page page =
(from p in mDataContext.Pages
where p.FileName == pageName
select p).SingleOrDefault();
return page;
}
Usage:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(string pageName, FormCollection formValues) {
Page updatedPage = mRepository.GetPage(pageName);
//TagString is a Form value, and is set via UpdateModel.
UpdateModel(updatedPage, formValues.ToValueProvider());
updatedPage.FileName = pageName;
//At this point NO changes should have been written to the database.
mRepository.Save();
//All changes should NOW be saved to the database.
return RedirectToAction("Index", "Pages", new { PageName = pageName });
}

Resources