I have a couple of tables with similar relationship structure to the standard Order, OrderLine tables.
When creating a data context, it gives the Order class an OrderLines property that should be populated with OrderLine objects for that particular Order object.
Sure, by default it will delay load the stuff in the OrderLine property but that should be fairly transparent right?
Ok, here is the problem I have: I'm getting an empty list when I go MyOrder.OrderLines but when I go myDataContext.OrderLines.Where(line => line.OrderId == 1) I get the right list.
public void B()
{
var dbContext = new Adis.CA.Repository.Database.CaDataContext(
"<connectionString>");
dbContext.Connection.Open();
dbContext.Transaction = dbContext.Connection.BeginTransaction();
try
{
//!!!Edit: Imortant to note that the order with orderID=1 already exists
//!!!in the database
//just add some new order lines to make sure there are some
var NewOrderLines = new List<OrderLines>()
{
new OrderLine() { OrderID=1, LineID=300 },
new OrderLine() { OrderID=1, LineID=301 },
new OrderLine() { OrderID=1, LineID=302 },
new OrderLine() { OrderID=1, LineID=303 }
};
dbContext.OrderLines.InsertAllOnSubmit(NewOrderLines);
dbContext.SubmitChanges();
//this will give me the 4 rows I just inserted
var orderLinesDirect = dbContext.OrderLines
.Where(orderLine => orderLine.OrderID == 1);
var order = dbContext.Orders.Where(order => order.OrderID == 1);
//this will be an empty list
var orderLinesThroughOrder = order.OrderLines;
}
catch (System.Data.SqlClient.SqlException e)
{
dbContext.Transaction.Rollback();
throw;
}
finally
{
dbContext.Transaction.Rollback();
dbContext.Dispose();
dbContext = null;
}
}
So as far as I can see, I'm not doing anything particularly strange but I would think that orderLinesDirect and orderLinesThroughOrder would give me the same result set.
Can anyone tell me why it doesn't?
You're just adding OrderLines; not any actual Orders. So the Where on dbContext.Orders returns an empty list.
How you can still find the property OrderLines on order I don't understand, so I may be goofing up here.
[Edit]
Could you update the example to show actual types, especially of the order variable? Imo, it shoud be an IQueryable<Order>, but it's strange that you can .OrderLines into that. Try adding a First() or FirstOrDefault() after the Where.
Related
I have an object that has a many-to-many relationship with another object. I am trying to write an update statement that doesn't result in having to delete all records from the many-to-many table first.
My data is:
StoredProcedure - StoredProcedureId, Name
Parameter - ParameterId, Name
StoredProcedure_Parameter - StoredProcedureId, ParameterId, Order
I have a UI for updating a stored procedured object (adding/removing parameters or changing the order of the parameters).
When I save, I end up at:
var storedProcedure = context.Sprocs.FirstOrDefault(s => s.SprocID == sproc.StoredProcedureId);
if (storedProcedure == null)
{
//do something like throw an exception
} else
{
storedProcedure.Name = sproc.Name;
//resolve Parameters many to many here
//remove all Params that are not in sproc.Params
//Add any params that are in sproc.Params but not in storedProcedure.Params
//Update the Order number for any that are in both
}
I know I could simply call .Clear() on the table and then reinsert all of the values with their current state (ensuring that all parameters that were removed by the UI are gone, new ones are added, and updated Orders are changed). However, I feel like there must be a better way to do this. Do many-to-many updates with EF usually get resolved by deleting all of the elements and reinserting them?
Here there is my code that I use and it works. The difference is that instead o having your 3 tables( StoredProcedure, StoredProcedure_Parameter and Parameter ) I have the following 3 tables: Order, OrdersItem(this ensure the many-to-many relation) and Item. This is the procedure that I used for updating or add an order, or after I change an existing OrderItem or add a new one to the Order.
public void AddUpdateOrder(Order order)
{
using (var db = new vitalEntities())
{
if (order.OrderId == 0)
{
db.Entry(order).State = EntityState.Added;
}
else
{
foreach (var orderItem in order.OrdersItems)
{
if (orderItem.OrderItemsId == 0)
{
orderItem.Item = null;
if (order.OrderId != 0)
orderItem.OrderId = order.OrderId;
db.Entry(orderItem).State = EntityState.Added;
}
else
{
orderItem.Order = null;
orderItem.Item = null;
db.OrdersItems.Attach(orderItem);
db.Entry(orderItem).State = EntityState.Modified;
}
}
db.Orders.Attach(order);
db.Entry(order).State = EntityState.Modified;
}
SaveChanges(db);
}
}
I have code that generates records based on my DataGridView. These records are temporary because some of them already exist in the database.
Crop_Variety v = new Crop_Variety();
v.Type_ID = currentCropType.Type_ID;
v.Variety_ID = r.Cells[0].Value.ToString();
v.Description = r.Cells[1].Value.ToString();
v.Crop = currentCrop;
v.Crop_ID = currentCrop.Crop_ID;
Unfortunately in this little bit of code, because I say that v.Crop = currentCrop,
now currentCrop.Crop_Varieties includes this temporary record. And when I go to insert the records of this grid that are new, they have a reference to the same Crop record, and therefore these temporary records that do already exist in the database show up twice causing duplicate key errors when I submit.
I have a whole system for detecting what records need to be added and what need to be deleted based on what the user has done, but its getting gummed up by this relentless tracking of references.
Is there a way I can stop Linq-To-Sql from automatically adding these temporary records to its table collections?
I would suggest revisiting the code that populates DataGridView (grid) with records.
And then revisit the code that operates on items from a GridView, keeping in mind that you can grab bound item from a grid row using the following code:
public object GridSelectedItem
{
get
{
try
{
if (_grid == null || _grid.SelectedCells.Count < 1) return null;
DataGridViewCell cell = _grid.SelectedCells[0];
DataGridViewRow row = _grid.Rows[cell.RowIndex];
if (row.DataBoundItem == null) return null;
return row.DataBoundItem;
}
catch { }
return null;
}
}
It is also hard to understand the nature of Crop_Variety code that you have posted. As the Crop_Variety seems to be a subclass of Crop. This leads to problems when the Crop is not yet bound to database and potentially lead to problems when you're adding Crop_Variety to the context.
For this type of Form application I normally have List _dataList inside form class, then the main grid is bound to that list, through ObjectBindingList or another way. That way _dataList holds all data that needs to be persisted when needed (user clicked save).
When you assign an entity object reference you are creating a link between the two objects. Here you are doing that:
v.Crop = currentCrop;
There is only one way to avoid this: Modify the generated code or generate/write your own. I would never do this.
I think you will be better off by writing a custom DTO class instead of reusing the generated entities. I have done both approaches and I like the latter one far better.
Edit: Here is some sample generated code:
[global::System.Data.Linq.Mapping.AssociationAttribute(Name="RssFeed_RssFeedItem", Storage="_RssFeed", ThisKey="RssFeedID", OtherKey="ID", IsForeignKey=true, DeleteOnNull=true, DeleteRule="CASCADE")]
public RssFeed RssFeed
{
get
{
return this._RssFeed.Entity;
}
set
{
RssFeed previousValue = this._RssFeed.Entity;
if (((previousValue != value)
|| (this._RssFeed.HasLoadedOrAssignedValue == false)))
{
this.SendPropertyChanging();
if ((previousValue != null))
{
this._RssFeed.Entity = null;
previousValue.RssFeedItems.Remove(this);
}
this._RssFeed.Entity = value;
if ((value != null))
{
value.RssFeedItems.Add(this);
this._RssFeedID = value.ID;
}
else
{
this._RssFeedID = default(int);
}
this.SendPropertyChanged("RssFeed");
}
}
}
As you can see the generated code is establishing the link by saying "value.RssFeedItems.Add(this);".
In case you have many entities for wich you would need many DTOs you could code-generate the DTO classes by using reflection.
I´m still having a hard time with Linq.
I need to write a Update Function tat receives an object that has a list. Actually, A region has a list of cities. I want to pass an object "Region" that has a name filed and a list of cities. The problem, is the city objects came from another context and I am unable to attach them to this context. I have been trying several functions, and always get an error like "EntitySet was modified during enumeration" or other. I am tring to make the code below work, but if anyone has a different approach please help.
public int Updateregion(region E)
{
try
{
using (var ctx = new AppDataDataContext())
{
var R =
(from edt in ctx.regiaos
where edt.ID == E.ID
select edt).SingleOrDefault();
if (R != null)
{
R.name = R.name;
R.description = E.description;
}
R.cities = null;
R.cities.AddRange(Edited.Cities);
ctx.SubmitChanges();
return 0 //OK!
}
}
catch (Exception e)
{
......
}
You can't attach objects retrieved from one datacontext to another, it's not supported by Linq-to-SQL. You need to somehow dettach the objects from their original context, but this isn't supported either. One can wonder why a dettach method isn't available, but at least you can fake it by mapping the list to new objects:
var cities = Edited.Cities.Select(city => new City {
ID = city.ID,
Name = city.Name,
/* etc */
});
The key here is to remember to map the primary key and NOT map any of the relation properties. They must be set to null. After this, you should be able to attach the new cities list, and have it work as expected.
I have the following code -
public void LoadAllContacts()
{
var db = new ContextDB();
var contacts = db.LocalContacts.ToList();
grdItems.DataSource = contacts.OrderBy(x => x.Areas.OrderBy(y => y.Name));
grdItems.DataBind();
}
I'm trying to sort the list of the contacts according to the area name that is contained within each contact. When I tried the above, I get "At least one object must implement IComparable.". Is there an easy way instead of writing a custom IComparer?
Thanks!
try this:
public void LoadAllContacts()
{
var db = new ContextDB();
var contacts = db.LocalContacts.ToList();
grdItems.DataSource = contacts.OrderBy(x => x.Areas.OrderBy(y => y.Name).First().Name);
grdItems.DataBind();
}
this will order the contacts by the first area name, after ordering the areas by name.
Hope this helps :)
Edit: fixed error in code. (.First().Name)
I was in a discussion with #AbdouMoumen but in the end I thought I'd provide my own answer :-)
His answer works, but there two performance issues in this code (both in the answer as in the original question).
First, the code loads ALL contacts in the db. This may or may not be a problem, but in general I would recommend NOT to do this. Many modern controls support paging/filtering out of the box, so you'd be better off supplying an not-yet-evaluated IQueryable<T> instead of List<T>. If however you need everything in memory, you should delay the ToList to the last possible moment.
Second, in AbdouMoumen's answer, there is a so-called 'SELECT N+1' problem. Entity Framework will by default use lazy loading to fetch additional properties. I.e. the Areas property will not be fetched from the database until it's accessed. In this case this will happen in the controls 'for loop', while it's ordering the result set by name.
Open up SQL Server Profiler to see what I mean: you will see a SELECT statement for all the contacts, and an additional SELECT statement for each contact that fetches the Areas for that contact.
A much better solution would be the following:
public void LoadAllContacts()
{
using (var db = new ContextDB())
{
// note: no ToList() yet, just defining the query
var contactsQuery = db.LocalContacts
.OrderBy(x => x.Areas
.OrderBy(y => y.Name)
.First().Name);
// fetch all the contacts, correctly ordered in the DB
grdItems.DataSource = contactsQuery.ToList();
grdItems.DataBind();
}
}
Is it one to one relation (Contact->Area)?
if yeah then try the following :
public partial class Contact
{
public string AreaName
{
get
{
if (this.Area != null)
return this.Area.Name;
return string.Empty;
}
}
}
then
grdItems.DataSource = contacts.OrderBy(x => x.AreaName);
I am trying to merge data between two identical schema databases using Linq-to-sql:
List<Contact> contacts = (from c in oldDb.Contact
select c).ToList();
contacts.ForEach(c => c.CreatedByID = 0);
newDb.Contact.InsertAllOnSubmit(contacts);
newDb.SubmitChanges();
Merely throws an "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." exception.
Other than doing the following, how else can this be done generically (in reasonable execution time):
List<Contact> contacts = (from c in oldDb.Contact
select c).ToList();
contacts.ForEach(c => { c.CreatedByID = 0; newDb.Contact.InsertAllOnSubmit(contacts); });
newDb.SubmitChanges();
along with:
private t GetNewObject<t>(t oldObj)
{
t newObj = (t)System.Reflection.Assembly.GetExecutingAssembly().CreateInstance(typeof(t).Name);
PropertyInfo[] props = typeof(t).GetProperties();
foreach (PropertyInfo _prop in props)
{
_prop.SetValue(newObj, _prop.GetValue(oldObj, null), null);
}
return newObj;
}
The problem is this method is rather slow when there's only 11 objects and 75 properties, I need to do this for a couple hundred thousand objects so any performance gains I can get at this end would greatly reduce overall run time.
Basically, is there any Detach or similar call I could do that will disconnect the existing objects from the old DataContext and connect them to the new DataContext. Without having to create all new objects for each and every one of the returned rows.
I didnt got the
contacts.ForEach(c => { c.CreatedByID = 0; newDb.Contact.InsertAllOnSubmit(contacts); });
shouldnt be something like
contacts.ForEach(c => {
Contact c2 = GetNewObject<Contact>(c);
c2.CreatedByID = 0;
newDb.Contact.InsertOnSubmit(c2);
});
also, here's a way of detaching the object from the old database: http://omaralzabir.com/linq_to_sql__how_to_attach_object_to_a_different_data_context/