Why does adding this line mess up my NHibernate mapping? - c#

I have a very simple ClassMap
public class ContentMap : ClassMap<Content>
{
public ContentMap()
{
// Basic property mapping
// Parent property mapping
References(x => x.Category).Column("CategoryId");
}
}
Using this, my mappings work perfectly fine and the Category property is not null.
If I try to add this line below the first reference
References(x => x.LastActive).Column("LastActiveSubCategoryId");
My mappings go wrong.
If LastActiveSubCategoryId is null, Category maps fine. If it is not null, LastActiveSubCategoryId will be set but then CategoryId is null.
The actual properties themselves are simple
public virtual Category Category { get; set; }
public virtual SubCategory LastActive { get; set; }
There is nothing complex in the Category or SubCategory mappings either. They look very similar to the ContentMap class (with only one reference line)
Any idea what would cause this behavior? Can I only use one Reference?
Update
I've looked at the SQL and this is what appears to be happening, hopefully someone can help me understand why.
The Content entity gets inserted into the database just fine with the CategoryId and LastActiveSubCategoryId null.
The Category entity gets inserted and then an update statement updates the Content, only updating the CategoryId field and nothing else.
If there was no SubCategory, everything would be fine at this point.
If there is a SubCategory, then a few statements later it is inserted and then the Category gets updated. In the update statement a few different values are being modified (some that don't need to be as they haven't changed since the insert), including the CategoryId and SubCategoryId. Except now CategoryId is null and LastActiveSubCategoryId is not.
So why would CategoryId be null on the update?
Update 2
The code I'm using to actually insert the objects is just for some basic tests at this point. These are the relevant bits of code:
Category category = new Category();
dao.Save(category);
Content content = new Content();
category.AddContent(content); // Adds it to a list, like in the tutorial mBotros posted
dao.Save(Content);
SubCategory subCategory = new SubCategory();
content.AddSubCategory(subCategory);
dao.Save(subCategory);
// On the Content class
public virtual void AddSubCategory(SubCategory subCategory)
{
SubCategories.Add(subCategory);
LastActive = subCategory;
}

There is a circular reference in your database schema, which can make inserting rows tricky.
Content references SubCategory, and SubCategory references Content. If you were to try to insert rows using plain old SQL, you would not be able to do this:
/* this line does not work because SubCategory 2 does not exist yet */
insert into Content (Id, LastActiveSubCategoryId) values (1, 2);
insert into SubCategory (Id, ContentId) values (2, 1);
You would instead have to do something like this:
insert into Content (Id, LastActiveSubCategoryId) values (1, null);
insert into SubCategory (Id, ContentId) values (2, 1);
update Content set LastActiveSubCategoryId = 2 where Id = 1;
You need to keep this in mind when you are persisting your NHibernate entities. Modify AddSubCategory to not set LastActive. First save the two entities, then close the loop.
// ... code to save Category and Content, then...
SubCategory subCategory = new SubCategory();
content.AddSubCategory(subCategory); // modified to *not* set LastActive
dao.Save(subCategory);
content.LastActive = subCategory;
dao.Update(content);

You mention a LastActiveId that is not in the code you've shown.
Are you by chance trying to map a column as both a Reference and a scalar Property?
Update: either your code snippet is still incomplete, or you're missing the transaction/flush that will cause the updates to happen. Also, do you have both a collection (not shown) called SubCategories and a property called LastActive?

Related

Getting Null Reference Exception while accessing columns from other tables

I am trying to do CRUD operations in c# using Linq on Products of the NWTraders Database. While Adding a new product, I am trying to display Supplier Name and Category Name instead of the Supplier ID and Category ID(which are foreign keys of the Product table).
I have tried to Add new product and it crashes soon after I press OK to save it on to the database and update my data grid. But I noticed that the new product is getting updated into the database with supplier and Category IDs as Null which is further preventing me from accessing the Product Windows form itself as it is not able to retrieve the IDs of the corresponding Supplier and Category Names that I am giving to the new product during the ADD.
cmbSupplierName.SelectedIndex seem to receiving a NULL value and this.product.Supplier.Company is throwing the Null Reference Exception. Same is the problem with category. If I handle them with the if condition, then it still throws me an exception in the below code.
private void LoadProductInformation()
{
lblProductHeader.Text = "Information about :" + this.product.ProductName;
txtProductID.Text = this.product.ProductID.ToString();
txtProductName.Text = this.product.ProductName;
// Not loading for Add Products as User has to enter the values.
if (this.Mode != ProductViewMode.Add)
{
cmbSupplierName.SelectedIndex = cmbSupplierName.FindString(this.product.Supplier.CompanyName);
cmbCategory.SelectedIndex = cmbCategory.FindString(this.product.Category.CategoryName);
txtQuantityPerUnit.Text = this.product.QuantityPerUnit;
txtUnitPrice.Text = this.product.UnitPrice.Value.ToString("C");
txtUnitsInStock.Text = this.product.UnitsInStock.Value.ToString();
txtUnitsOnOrder.Text = this.product.UnitsOnOrder.Value.ToString();
txtReorderLevel.Text = this.product.ReorderLevel.Value.ToString();
chkDiscontinued.Checked = (this.product.Discontinued == true);
}
}
public void LoadDGVProducts(IEnumerable<Product> products)
{
// If there are no products, do nothing and return from the function.
if (products == null) return;
FetchData(); //fetching all the serach parameters
this.dgvProducts.SelectionChanged -= new System.EventHandler(this.DGVProducts_SelectionChanged);
if (dgvProducts.RowCount == 0)
FormatDGVProducts();
dgvProducts.Rows.Clear();
// Go through every product in the product collection and
// add it as a row in the dgv
foreach (Product prod in products)
{
dgvProducts.Rows.Add(
prod.ProductID, // The ID will not actually be shown since it is given to a column that has the Visible property set to False.
prod.ProductName,
prod.Supplier.CompanyName,
prod.Category.CategoryName,
prod.QuantityPerUnit,
prod.UnitPrice.Value.ToString("C"),
prod.UnitsInStock,
prod.UnitsOnOrder,
prod.ReorderLevel,
prod.Discontinued
);
...........................
}
}
Due to the supplier and category IDs receiving Null values on database, it is throwing me the exceptions at the 'foreach' as it won't let product to display on data grid if even one of the values in null in that condition.
I don't know where I am supposed to connect Supplier ID to Name for it to not receive Null values on the database.

Navigation Property Not being Added In Entity Framework 7 using vNext

public void AddMeal(MealModel mealModel)
{
using (var context enter code here= new HealthContext())
{
var meal = new Meal
{
MealNumber = mealModel.MealNumber,
MealEntries = new List<MealEntry> { new MealEntry { FoodId = 1, MealEntryNumber = 1, Calories = 250, MealId = 1 } },
DayId = mealModel.Date
};
context.Meals.Add(meal);
context.SaveChanges();
}
}
I am using Entity Framework 7 with code first migrations and am trying to add a new "Meal" to the database.
The "Meal" is added successfully with the 2 columns listed above, but the "MealEntries" are not added. MealEntries is an ICollection of MealEntry that is used as a navigation property that exists on the "Meal" Entity.
In this code example I even hard-coded the new "List" to see if that would work, but even that is not being added to the database.
1 odd thing I noticed while debugging the code was that after stepping over the "Add" command, all of the primary keys and/or foreign keys were set to negative values by EF, except for the "Id" of each "MealEntry" in the "MealEntries list. It's almost like EF is not tracking this list and thus will not update it.
What do I need to do to have a navigation property that is a list be "bulk" added 1 by 1 into its respective table in the database when I add a "Meal"?
In EntityFramework 7, at this point in time, child members are not automatically added. You have to explicitly add them yourself.
context.Meals.Add(meal);
context.MealEntries.AddRange(meal.MealEntries);
context.SaveChanges();
Relevant github issue discussing this.

Create a computed field using Entity Framework 6

Consider the following model for a EF 6 entity:
Id (int, nullable:false, identity: true)
Name (string)
Number (string).
I want number to persisted as combination of letter and id field. For example if during insert id valuse is going to be 1, I want number to be A00000001. I tried using DatabaseGenerated attribute on Number but that did not work. Problem is at time of insert EF will not know what identity value is. Is there a way to define a trigger to do so or is there some other method I can achieve this.
Thanks!
If you don't need to save the computed field to the database and are using POCOs for Entities you can just define an unmapped field like this:
[NotMapped]
public string Number
{
get { return Name + Id.ToString(); }
}
Try first inserting the item to the database:
Item myItem = new Item(){Name = "MyName"};
dbcontext.Items.Add(myItem);
dbcontext.SaveChages();
Then bring the item from the database and update it:
Item insertedItem = dbcontext.Items.Single(i => i.Id == myItem.Id);
insertedItem.Number = insertedItem.Name + insertedItem.Id.toString();
dbcontext.SaveChages();

help inserting new row into a db using linq

Here is a snippet of my code:
else
{
SubCategory subCat = new SubCategory
{
SubCategoryName = name,
Active = false,
CategoryID=Convert.ToInt32(ddlCategory.SelectedValue)
};
db.SubCategories.InsertOnSubmit(subCat);
}
db.SubmitChanges();
The following line is causing an error:
CategoryID=Convert.ToInt32(ddlCategory.SelectedValue)
I have confirmed that the SelectedValue in my DDL is an int, and that the database is expecting an int, so I don't understand why asp.net gives me a YSOD saying "Input string was not in a correct format."
If I assign CategoryID a number manually, it works.
EDIT: The problem was because I was populating the drop down list in my code behind and I didn't wrap it in a (!IsPostBack). So it was destroying the list, repopulating it and setting the index at 0 each time on post back.
so try this then
else
{
int cat = Convert.ToInt32(ddlCategory.SelectedValue);
SubCategory subCat = new SubCategory
{
SubCategoryName = name,
Active = false,
CategoryID = cat
};
db.SubCategories.InsertOnSubmit(subCat);
}
Where does ddlCategory come from? What's the value of ddlCategory.SelectedValue? When this gets run? Clearly it's not a valid integer-representing string at this point in time.
Maybe try
Int.Parse(ddlCategory.SelectedValue);
is the categoryID foreign key for another table? if yeah then try to pull the Category object and then assign it to the subCat.Category and see if it works (the relation must be set in the DBML designer)
one last thing: when exactly it throws the exception?

LINQ to SQL Insert Multiple Tables Question

I have 3 tables. A primary EmploymentPlan table with PK GUID EmploymentPlanID and 2 FK's GUID PrevocServicesID & GUID JobDevelopmentServicesID. There are of course other fields, almost exclusively varchar(). Then the 2 secondary tables with the corresponding PK to the primary's FK's.
I am trying to write the LINQ INSERT Method and am struggling with the creation of the keys. Say I have a method like below. Is that correct? Will that even work? Should I have seperate methods for each?
Also, when inserting I didn't think I needed to provide the PK for a table. It is auto-generated, no?
Thanks.
public static void InsertEmploymentPlan(int planID, Guid employmentQuestionnaireID, string user, bool communityJob, bool jobDevelopmentServices, bool prevocServices, bool transitionedPrevocIntegrated, bool empServiceMatchPref)
{
using (var context = MatrixDataContext.Create())
{
var empPrevocID = Guid.NewGuid();
var prevocPlan = new tblEmploymentPrevocService
{
EmploymentPrevocID = empPrevocID
};
context.tblEmploymentPrevocServices.InsertOnSubmit(prevocPlan);
var empJobDevID = Guid.NewGuid();
var jobDevPlan = new tblEmploymentJobDevelopmetService()
{
JobDevelopmentServicesID = empJobDevID
};
context.tblEmploymentJobDevelopmetServices.InsertOnSubmit(jobDevPlan);
var empPlan = new tblEmploymentQuestionnaire
{
CommunityJob = communityJob,
EmploymentQuestionnaireID = Guid.NewGuid(),
InsertDate = DateTime.Now,
InsertUser = user,
JobDevelopmentServices = jobDevelopmentServices,
JobDevelopmentServicesID =empJobDevID,
PrevocServices = prevocServices,
PrevocServicesID =empPrevocID,
TransitionedPrevocToIntegrated =transitionedPrevocIntegrated,
EmploymentServiceMatchPref = empServiceMatchPref
};
context.tblEmploymentQuestionnaires.InsertOnSubmit(empPlan);
context.SubmitChanges();
}
}
I understand I can use more then 1 InsertOnSubmit(), See this question, I just don't understand how that would apply to my situation and the PK/FK creation.
The pk can be auto generated when the table's definition in the db does it for you. Also the property for the corresponding pk on the linq model has to configured to be updated after the insert, so it gets the auto generated ID.
I don't think the relation on those tables is on your linq model. Otherwise you should be able to do:
using (var context = MatrixDataContext.Create())
{
var empPlan = new tblEmploymentQuestionnaire
{
CommunityJob = communityJob,
InsertDate = DateTime.Now,
InsertUser = user,
JobDevelopmentServices = jobDevelopmentServices,
JobDevelopmentService = new tblEmploymentJobDevelopmetService(),
PrevocServices = prevocServices,
PrevocService = new tblEmploymentPrevocService(),
PrevocServicesID =empPrevocID,
TransitionedPrevocToIntegrated =transitionedPrevocIntegrated,
EmploymentServiceMatchPref = empServiceMatchPref
};
context.tblEmploymentQuestionnaires.InsertOnSubmit(empPlan);
context.SubmitChanges();
}
ps. not having the relation in the model is a design decision, so the above doesn't mean that's the only way to do it. The way you showed (with the extra SubmitChanges calls as in the other answer) is perfectly valid, just responds to a different design.
I think the issue is (if I understand it correctly) you are deferring the inserting, except you don't know it...
Since you're creating FKs but differing their insertion until the end, it doesn't know what to do, so when you try to create the main entry it's enforcing the FK constraints (which might not exist yet), thus failing. Try creating the FK entries and actually submitting the changes to the database before insert the main entry.
For example, say you have the following tables:
Child
Toy
ToyOwner
ToyOwner has FK constraints on Child and Toy. If the entries are missing in that table, you will not be able to insert an entry into ToyOwner. So you'd have to do something like the following:
Child myChild;
Toy myToy;
//Queue up the changes that are going to be submitted
InsertOnSubmit(myChild)
InsertOnSubmit(myToy)
//Submit the queue
SubmitChanges();
//Now that those FKs are filled, we can insert the main entry with those FK values
ToyOwner = new myToyOwner
myToyOwner.Child = myChild
myToyOwner.Toy = myToy
//And insert the new queue into the DB
InsertOnSubmit(myToyOwner)
SubmitChanges();

Categories

Resources