If I retrieve an object from the database using Linq to SQL in one method using an instance of DataContext, which closes on exit of that method, can I edit the object in a different method and with a different DataContext and have the changes take effect in the database?
i.e. Would something like the below work?
public void Foo()
{
using (var db = new DataContext())
{
Bar a = this.GetBar();
if (a != null)
{
a.Property1 = true;
db.SubmitChanges();
}
}
}
private Bar GetBar(string val)
{
using (var db = new DataContext())
{
return db.FirstOrDefault(x => x.Property2 == val);
}
}
There should be some kind of Attach method
Something like:
public void Foo()
{
using (var db = new DataContext())
{
Bar a = this.GetBar();
if (a != null)
{
db.Bars.Attach(a);
a.Property1 = true;
db.SubmitChanges();
}
}
}
Related
I've got method AddOrUpdateFruitMetaData which should add or update record in FruitMetaData table.
Unfortunately I'm keep getting an errors:
Violation of PRIMARY KEY constraint 'PK_FruitMetaData'. Cannot insert duplicate key in object 'dbo.FruitMetaData'. The duplicate key value is (0, COLOR). The statement has been terminated.
How this is even possible even when I'm trying to find existing record before?
Also using AddOrUpdate() from System.Data.Entity.Migrations instead of Add() is not helping me.
Call stack looks that:
In my main class in Parallel.ForEach with multiple for loop looking like that:
Parallel.ForEach(args)
{
for(something)
{
var fruit = new Fruit();
fruit.FruitId = uniqueId;
UpdateOrCreateFruitMetaData(fruit, "Color", "Blue")
}
}
I'm calling method:
private void UpdateOrCreateFruitMetaData(Fruit fruit, string metaType, string value)
{
var fruitMetaData = new FruitMetaData();
fruitMetaData.FruitId = fruit.FruitId;
fruitMetaData.MetaType = metaType;
fruitMetaData.Value = value;
using (var db = Context.DB)
{
db.AddOrUpdateFruitMetaData(fruitMetaData);
}
}
That method is using Context.DB which is object containing new DBEntity() and also Dispose(). So my Entity Context is disposed every time.
Then I'm calling next method AddOrUpdateFruitMetaData():
AddOrUpdateFruitMetaData(FruitMetaData fruitMetaData)
{
lock (thisLock)
{
try
{
var fmd = db.FruitMetaData.Where(x => x.FruitId == fruitMetaData.FruitId)
.Where(x => x.MetaType == fruitMetaData.MetaType)
.FirstOrDefault();
if (fmd == null)
db.FruitMetaData.Add(fruitMetaData);
else
fmd.Value = fruitMetaData.Value;
db.SaveChanges();
}
catch (Exception ex)
{
Log.Error($"AddOrUpdateFruitMetaData. FruitId:{fruitMetaData.FruitId} MetaType:{fruitMetaData.MetaType}", ex);
}
}
}
[EDIT] To explain using (var db = Context.DB) :
My Context class contains DbData DB Property which looks like that :
public DbData DB
{
get { return new DbData(); }
}
And DbData class looks like that:
public class DbData : IDisposable
{
private DBEntity db;
public DbData()
{
db = new FruitDBEntity();
}
// This class contains that problematic method
AddOrUpdateFruitMetaData(FruitMetaData fruitMetaData)(...)
}
So each time I'm getting Context.DB property I am actually creating new Entity Context.
I'm trying to make a master-detail Web Form working with Entity Framework and performing insert and update on the same page. I'm new at EF, so I must be making a lot of mistakes here. Can you help me pointing me what's the best practices to perform insert/update on EF? What am I doing wrong here?
In this code, the "New" mode works well, but the "Edit" mode gets this error: "An entity object cannot be referenced by multiple instances of IEntityChangeTracker".
OrdersEntities ordersEntities = new OrdersEntities();
private Order myOrder
{
get { return (Order)Session["myOrder"]; }
set { Session["myOrder"] = value; }
}
public DataTable dtOrderDetails
{
get { return (DataTable)ViewState["dtOrderDetails"]; }
set { ViewState["dtOrderDetails"] = value; }
}
private string Mode
{
get { return (string)ViewState["mode"]; }
set { ViewState["_modo"] = value; }
}
private void btnSaveOrder_Click(object sender, EventArgs e)
{
if (dtOrderDetails.Rows.Count > 0)
{
using (ordersEntities)
{
using (var contextTransaction = ordersEntities.Database.BeginTransaction())
{
try
{
if (Mode == "New")
{
Order newOrder = new Order();
OrderDetails newOrderDetails;
int maxOrderNumber = ordersEntities.Order.Select(o => o.OrderNumber).DefaultIfEmpty(0).Max();
maxOrderNumber++;
newOrder.OrderNumber = maxOrderNumber;
newOrder.Date = DateTime.ParseExact(txtOrderDate.Text, "dd/MM/yyyy", CultureInfo.InvariantCulture);
newOrder.CustomerID = Convert.ToInt32(ddlCustomer.SelectedValue);
newOrder.Status = 1;
ordersEntities.Orders.Add(newOrder);
foreach (DataRow dt in dtOrderDetails.Rows)
{
newOrderDetails = new OrderDetails();
newOrderDetails.OrderNumer = maxOrderNumber;
newOrderDetails.ProductId = Convert.ToInt32(dt["ProductId"]);
newOrderDetails.Quantity = Convert.ToInt32(dt["Quantity"]);
ordersEntities.OrderDetails.Add(newOrderDetails);
}
ordersEntities.SaveChanges();
contextTransaction.Commit();
myOrder = newOrder;
}
if (Mode == "Edit")
{
Order editedOrder = myOrder;
OrderDetails editedOrderDetails;
editedOrder.Date = DateTime.ParseExact(txtOrderDate.Text, "dd/MM/yyyy", CultureInfo.InvariantCulture);
editedOrder.CustomerID = Convert.ToInt32(ddlCustomer.SelectedValue);
ordersEntities.Order.Attach(editedOrder);
ordersEntities.Entry(editedOrder).State = System.Data.Entity.EntityState.Modified;
editedOrder.OrderDetails.Clear();
foreach (DataRow dt in dtOrderDetails.Rows)
{
editedOrderDetails = new OrderDetails();
editedOrderDetails.OrderNumer = editedOrder.OrderNumber;
editedOrderDetails.ProductId = Convert.ToInt32(dt["ProductId"]);
editedOrderDetails.Quantity = Convert.ToInt32(dt["Quantity"]);
ordersEntities.OrderDetails.Add(editedOrderDetails);
}
ordersEntities.SaveChanges();
contextTransaction.Commit();
}
}
catch (Exception ex)
{
contextTransaction.Rollback();
}
}
}
}
}
Here is how you should approach it.
It would be best if you abstract the DbContext away, with this simple interface:
public interface IDataRepository : IDisposable
{
IDbSet<Order> Orders { get; set; }
void Save();
}
Of course, your implementation of IDataRepository is based on EntityFramework. Note that you will need to have a dataRepositoryConnection connection string in your web.config file:
public class EfDataRepository : DbContext, IDataRepository
{
public EfDataRepository() : base("dataRepositoryConnection")
{
}
public IDbSet<Order> Orders { get; set; }
public void Save()
{
this.SaveChanges();
}
}
In my experience, you also need a 'factory', which gives you a new instance of the data repository. This allows you to be the 'owner' of the instance, and you can safely dispose it. Note that the interaction with the DataContext should be minimal - you do your Unity of Work and get rid of it. Don't reuse! You will see it as an example below.
public class DataRepositoryFactory<T> where T : IDataRepository
{
private Type dataRepositoryImplementationType;
public DataRepositoryFactory(T dataRepositoryImplementation)
{
if (dataRepositoryImplementation == null)
{
throw new ArgumentException("dataRepositoryImplementation");
}
this.dataRepositoryImplementationType = dataRepositoryImplementation.GetType();
}
public T Create()
{
return (T)Activator.CreateInstance(this.dataRepositoryImplementationType);
}
}
In your controller (if it were MVC app), or Page backend (forms), it would be best if you use Microsoft Unity to get an instance of DataRepositoryFactory. For now, a manual construction would suffice too.
IDataRepository dataRepository = new EfDataRepository();
var dataRepositoryFactory = new DataRepositoryFactory<IDataRepository>(dataRepository);
Also, you don't need all this Transaction/Commit stuff you have put. It should be transparent for you. EF supports it implicitly, you don't have to be explicit about it.
// See, now you are the 'owner' of the dataRepository
using (var dataRepository = this.dataRepositoryFactory.Create())
{
if (Mode == "New")
{
Order newOrder = new Order();
// This doesn't make sense. Either generate a random order number (e.g. a Guid), or just use the Order.Id as an order number, although I don't recommend it.
int maxOrderNumber = dataRepository.Orders.Select(o => o.OrderNumber).DefaultIfEmpty(0).Max();
maxOrderNumber++;
newOrder.OrderNumber = maxOrderNumber;
newOrder.Date = DateTime.ParseExact(txtOrderDate.Text, "dd/MM/yyyy", CultureInfo.InvariantCulture);
newOrder.CustomerID = Convert.ToInt32(ddlCustomer.SelectedValue);
newOrder.Status = 1;
dataRepository.Orders.Add(newOrder);
foreach (DataRow dt in dtOrderDetails.Rows)
{
OrderDetails newOrderDetails = new OrderDetails();
newOrderDetails.OrderNumer = maxOrderNumber;
newOrderDetails.ProductId = Convert.ToInt32(dt["ProductId"]);
newOrderDetails.Quantity = Convert.ToInt32(dt["Quantity"]);
newOrder.OrderDetails.Add(newOrderDetails);
}
myOrder = newOrder;
}
if (Mode == "Edit")
{
Order editedOrder = dataRepository.Orders.FirstOrDefault(o => o.Id == myOrder.Id);
editedOrder.Date = DateTime.ParseExact(txtOrderDate.Text, "dd/MM/yyyy", CultureInfo.InvariantCulture);
editedOrder.CustomerID = Convert.ToInt32(ddlCustomer.SelectedValue);
editedOrder.OrderDetails.Clear();
foreach (DataRow dt in dtOrderDetails.Rows)
{
OrderDetails editedOrderDetails = new OrderDetails();
editedOrderDetails.OrderNumer = editedOrder.OrderNumber;
editedOrderDetails.ProductId = Convert.ToInt32(dt["ProductId"]);
editedOrderDetails.Quantity = Convert.ToInt32(dt["Quantity"]);
editedOrder.OrderDetails.Add(editedOrderDetails);
}
}
dataRepository.Save();
}
Also, I am pretty sure you have setup the relation between Order and OrderDetails classes incorrectly, in your EF code-first approach.
This is just wrong:
OrderDetails newOrderDetails = new OrderDetails();
newOrderDetails.OrderNumer = maxOrderNumber;
If you post them here, I can fix them for you.
I'm adding a new item to my CSLA BusinessList. But I can only add it with a 0 primary key because my items which I add is always null. And if I try: "Item temp= new item{...}" is this item not child of the list.
I'll post my Add function first and then some dataportals.
public void ExecuteNew(object obj)
{
if (Model != null)
{
Temp = Model.AddNew();
//Temp.FarbauswahlNr = 123;
//Temp.Kurztext = this.Kurztext;
//Temp.Ressource = this.Ressource;
//Temp.Vari1 = this.Vari1;
Model = Model.Save();
}
}
Now some DataPortals from my Business class and BusinessList class
protected override void DataPortal_Update()
{
using (var ctx = Csla.Data.ObjectContextManager<Datenbank.TestDBEntities>.GetManager(Business.EntitiesDatabase.Name))
{
Child_Update();
}
}
protected override void Child_Create()
{
base.DataPortal_Create();
BusinessRules.CheckRules();
}
private void Child_Insert()
{
using (var ctx = Csla.Data.ObjectContextManager<TestDBEntities>.GetManager(EntitiesDatabase.Name))
{
try
{
var data = new Datenbank.Farbe();
data.Kurztext = ReadProperty<string>(KurztextProperty);
data.Ressource = ReadProperty<string>(RessourceProperty);
data.Var1 = ReadProperty<bool>(Vari1Property);
data.Vari2 = ReadProperty<string>(Vari2Property);
ctx.ObjectContext.Farben.AddObject(data);
ctx.ObjectContext.SaveChanges();
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
}
}
Possible Errors :
NullReferebceException was unhadeld ( because temp is always null )
The Problem was: CSLA 4.5 has got a bug wich let your UI Freeze after the Save Command. Now im using CSLA 4.1 and it works just fine.
I have the following code and I cannot achieve saving the changes.
The parameter of my method is a string containing the RefCode of a product I want to modify in the database, then the query is pulling the BaseProduct that is supposed to be modified.
(I tried to simplify the code and set it in English, so I have probably introduced some syntactic errors, but in my code in debug mode, I get all the info FROM the DB). Is there something wrong with the "select new" in the Linq query ?
public static void UpdateProduct(ViewProduct productToUpdate)
{
using (var context = new my_Entities())
{
var BaseProduct = (from prod in context.Product
where prod.Ref == productToUpdate.BaseProduct.RefPrd
select new ViewBaseProduct
{
RefPrd = prod.Ref,
DescrPrd = prod.DescrPrd,
NormeCe = (bool)prod.NormeCE
}).FirstOrDefault();
if (BaseProduct != null)
{
//BaseProduct.NormeCe = false;
BaseProduct = productToUpdate.BaseProduct;
context.SaveChanges();
}
}
}
But BaseProduct is a ViewBaseProduct object, is ViewBaseProduct a entity class? It seems it is a ViewModel class.
You have to get de Product entity, modify his fields and savechanges. It seems you only apply changes to the ViewModel class.
Try this:
public static void UpdateProduct(ViewProduct productToUpdate)
{
using (var context = new my_Entities())
{
var BaseProduct = (from prod in context.Product
where prod.Ref == productToUpdate.BaseProduct.RefPrd)
.FirstOrDefault();
if (BaseProduct != null)
{
//BaseProduct.NormeCe = false;
BaseProduct.field1 = productToUpdate.BaseProduct.field1;
BaseProduct.field2 = productToUpdate.BaseProduct.field2;
//update the necesary fields
//......
context.SaveChanges();
}
}
}
This won't work that way. You should use the CurrentValues.SetValues() method:
contexte.Entry(BaseProduct).CurrentValues.SetValues(productToUpdate.BaseProduct);
I think you have to Try this
public static void UpdateProduct(ViewProduct productToUpdate)
{
using (var contexte = new my_Entities())
{
var BaseProduct = (from prod in contexte.Product
where prod.Ref == productToUpdate.BaseProduct.RefPrd
select new ViewBaseProduct
{
RefPrd = prod.Ref,
DescrPrd = prod.DescrPrd,
NormeCe = (bool)prod.NormeCE
}).FirstOrDefault();
if (BaseProduct != null)
{
BaseProduct.BaseProduct.RefPrd=productToUpdate.BaseProduct.RefPrd
BaseProduct.BaseProduct.DescrPrd=productToUpdate.BaseProduct.DescrPrd
BaseProduct.BaseProduct.NormeCE==(bool)productToUpdate.BaseProduct.NormeCE
contexte.SaveChanges();
}
}
}
is it possible in Db4o to load new objects into persistent IObjectContainer?
I have a desktop application which opens one connection (IObjectContainer) when started. if I query all objects with:
var objects = from DummyClass foo in session
select foo
it selects all objects perfectly. However, if another client adds new classes after this, the same query still selects the same objects, without new ones.
I also know about:
session.Ext().Refresh(obj, int.MaxValue);
but I don't have even not activated references to new objects so there. How to refresh new objects?
Just note: I don't want to open/close session every time I need some data, I want to take advantage of OODB (Transparent activation, object persistance since loaded etc.)
Thank you
UPDATE (code example for better understanding)
// store one class to fill database with some data
using (var mainSession = SessionFactory.CreateNewConnection())
{
mainSession.Store(new DummyClass());
mainSession.Commit();
}
using (var mainSession = SessionFactory.CreateNewConnection())
{
// returns one object
var objects = from DummyClass foo in session
select foo;
using (var secondSession = SessionFactory.CreateNewConnection())
{
secondSession.Store(new DummyClass());
secondSession.Commit();
}
// this loop reload objects known for mainSession (which is not new object)
foreach (var obj in objects2)
{
mainSession.Ext().Refresh(obj, int.MaxValue);
}
// new DummyClass is commited but still not visible (Read-Commited isolation)
// returns one object
var objects2 = from DummyClass foo in session
select foo;
}
using (var mainSession = SessionFactory.CreateNewConnection())
{
// returns two objects
var objects = from DummyClass foo in session
select foo;
}
I need something like:
// refresh all objects of DummyClass
session.Ext().Refresh(typeof(DummyClass), int.MaxValue);
You may use Commited events:
using Db4objects.Db4o;
using Db4objects.Db4o.Events;
using Db4objects.Db4o.IO;
using Db4objects.Db4o.Ext;
namespace PushedUpdates
{
class Program
{
static void Main()
{
var config = Db4oEmbedded.NewConfiguration();
config.File.Storage = new MemoryStorage();
var container = Db4oEmbedded.OpenFile(config, "IN-MEMORY");
var client = container.Ext().OpenSession();
var clientEvents = EventRegistryFactory.ForObjectContainer(client);
clientEvents.Committed += (s, a) =>
{
foreach(IObjectInfo added in a.Added)
{
System.Console.WriteLine(added.GetObject());
}
};
container.Store(new Item { Value = 1 } );
container.Commit();
container.Store(new Item { Value = 2 });
container.Commit();
container.Store(new Item { Value = 3 });
container.Commit();
client.Close();
container.Close();
}
}
class Item
{
public int Value { get; set; }
public override string ToString()
{
return "" + Value;
}
}
}
Did your client call the commit() method after storing the data? Otherwise the new data will not be available for other clients.