How to insert/update master-detail in Entity Framework? - c#

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.

Related

Is it possible to clear objects with the get property after calling the class?

this below to sample code;
private ExampleStatus _status;
public ExampleStatus status
{
get
{
if (_status == null) _status = new ExampleStatus();
//if (_status.receivedData) _status.receivedData = false; //this line is incorrect !
return _status;
}
}
public class ExampleStatus
{
public int Id { get; set; }
public string Name { get; set; }
public bool receivedData { get; set; }
//I don't want to use this method
public void Clear()
{
Id = 0;
Name = string.Empty;
receivedData = false;
}
}
int stateType = 0;
void ContinuousLoop(ExampleStatus statusObj)
{
while (true)
{
//I don't want to use the options below
//statusObj.Clear();
//or
//statusObj = new ExampleStatus();
if (stateType == 0)
{
statusObj.Id = 0;
statusObj.Name = "Firs Status";
}
else if (stateType == 1)
{
statusObj.Id = 1;
statusObj.Name = "Second Status";
statusObj.receivedData = true;
}
else if (stateType == 2)
{
statusObj.Id = 2;
statusObj.Name = "Third Status";
}
}
}
void RunThread()
{
var t1 = new Thread(() =>
{
ContinuousLoop(status);
});
t1.Start();
}
Is it possible to set default values ​​without a method or new instance, as shown in the example?
Actually that's why I'm asking this question:
I will use the class I have defined in many places. I will need to add a block of code, such as the Clear method, to every place I use it.
I'm also curious about one more thing. If I assign a new instance every time to reset my objects, does this cause problems in memory?
I know more or less how garbage collections work. However, they say that in practice it does not work as said in theory.
So if I add "IDisposable" to my Class, it would tell the garbage collector: Welcome, I'm a litter. Will you take me too?

C# Generic parameter from a string variable

I have two classes, Customer and Country. Customer has a property called HomeCountry, which i have decorated with a custom attribute called "Lookup" and takes a string parameter "Country". The purpose is, when I am using the Customer class, the item in HomeCountry must exist in the Country class (which happens to be a list).
I am using reflection to iterate the Customer class, it finds the attribute and i want it to check the value in the list of country items. So far I have:
foreach (PropertyInfo _pi in object.GetType().GetProperties()) {
IEnumerable<Attribute> _attrs = _pi.GetCustomAttributes();
foreach (Attribute _a in _attrs) {
Object obj = Activator.CreateInstance(_type, null);
// what goes here?
}
}
I have a method:
public T Populate<T>(params string[] _parameters)
I think i want to do
List<obj> v = populate<obj>();
or
List<typeof(obj)> v = populate<typeof(obj)>();
but obviously nothing works! Can anybody help me ?
Thanks
OK i will try and provide a full example:
I have a CUSTOMER_ORDER class:
public class CUSTOMER_ORDER {
public CUSTOMER_ORDER() {}
[Key(0)]
public string OrderNumber {get;set;}
public MPCCOM_SHIP_VIA ShipVia {get;set;}
}
Then the MPCCOM_SHIP_VIA class:
public class MPCCOM_SHIP_VIA {
public MPCCOM_SHIP_VIA() {}
[Key(0)]
public string ID {get;set;}
public string Description {get;set;}
}
I have a method called Populate< T > which takes a class and then uses reflection to loop all the properties and build a select statement, executes it, and then returns the data and populates the object:
public T Populate<T>(params string[] #Parameters)
{
Type _t = typeof(T);
dynamic _o = Activator.CreateInstance(typeof(T), null);
SqlBuilder _sb = new SqlBuilder();
_sb.Table = string.Format("{0}.{1}", _Owner, _t.Name.ToString());
foreach (PropertyInfo p in _t.GetProperties(Utilities.BindingFlags))
{
if (p.GetMethod.IsPrivate == false) _sb.Fields.Add(p.Name.ToString());
IEnumerable<Attribute> _attrs = p.GetCustomAttributes();
foreach (Attribute _a in _attrs)
{
if (_a.TypeId.ToString().Equals(typeof(Key).FullName))
{
int _position = ((Key)_a).Position;
try
{
string _parameter = #Parameters[_position];
_sb.Where.Add(string.Format("{0} = '{1}'", p.Name, _parameter));
}
catch {}
}
}
}
using (OleDbCommand _cmd = new OleDbCommand())
{
_cmd.Connection = this._cn;
_cmd.CommandText = _sb.SQL;
if (_trn != null) _cmd.Transaction = _trn;
_cmd.CommandType = System.Data.CommandType.Text;
using (OleDbDataReader _reader = _cmd.ExecuteReader())
{
if (_reader.Read())
{
for (int x = 0; x < _reader.FieldCount; x++)
{
foreach (PropertyInfo p in _t.GetProperties(Utilities.BindingFlags))
{
if (p.GetMethod.IsPrivate == false)
{
if (p.Name.Equals(_reader.GetName(x).ToString()))
{
dynamic _val = _reader.GetValue(x);
if (p.ReflectedType.BaseType.Name.Equals(""))
{
// what goes here!
}
try
{
p.GetSetMethod(true).Invoke(_o, new object[] { _val });
}
catch { }
break;
}
}
}
}
}
else
{
throw new DatabaseObjectNotFound(_t.Name.ToString(), string.Join(",",#Parameters));
}
}
}
return (T)_o;
}
So, as i read an order, the source DB gets the key to the MPCCOM_SHIP_VIA in the respective field, i want to call the same Populate method against the MPCCOM_SHIP_VIA object with the key. I hope this makes more sense demonstrating what i want to do. And thanks
After some hunting around, this is the answer i was looking for...
MethodInfo method = typeof(class).GetMethod("Populate");
method = method.MakeGenericMethod(p.PropertyType);
_val = method.Invoke(class, new object[] { _prms });
I guess my issue was i was asking the wrong question!

Entity framework error as"New transaction is not allowed because there are other threads running in the session

We are using entity framework codefirst approach
I am new to entity framework and I am facing error while trying to do "New transaction is not allowed because there are other threads running in the session.
public class DatabaseBackup : IDataBackup
{
private readonly IMonarchDbContext m_db;
public DatabaseBackup(IMonarchDbContext podb)
{
if (podb == null)
throw new ArgumentNullException("podb");
m_db = podb;
}
public DBBackupHistory GetLatestBackupHistory(DBBackupFrequency backupFrequency = DBBackupFrequency.Periodic)
{
DBBackupHistory result = null;
// get the backup history of the given backuptype and populate the objects
var configId = m_db.DBBackupConfigurations.Where(c => c.ScheduleType == (int)backupFrequency && c.BackupStatus == 1).Distinct().Select(c => c.ConfigurationId).DefaultIfEmpty(-1).First();
if (configId > 0)
{
result = m_db.DBBackupHistorys.Where(b => b.Status == 1 && b.ConfigurationId == configId).OrderByDescending(lb => lb.BackupDatetime).FirstOrDefault();
}
return result;
}
public IEnumerable<DBBackupConfiguration> GetAllConfiguration()
{
var result = m_db.DBBackupConfigurations.Where(c => c.BackupStatus == 1).OrderByDescending(c => c.ConfigurationId);
return result;
}
public void Backup(DBBackupConfiguration config, int fileIndex)
{
Console.WriteLine("Running DB Backup type {0} to device {1}", (DBBackupType)config.BackupType, fileIndex);
m_db.StoredProc.SPBackup(config, fileIndex);
}
I am calling the below methods in another class as follows
private readonly IDataBackup m_dataBackup;
public int PerformBackup(int defaultPollIntervalInMinutes = 15)
{
// polling interval in Minutes
int pollInterval = defaultPollIntervalInMinutes;
int fileIndex = getCurrentDumpFileIndex();
// check for the backup configuration
var configurations = m_dataBackup.GetAllConfiguration();
foreach (var config in configurations)
{
var lastBackup = m_dataBackup.GetLatestBackupHistory(DBBackupFrequency.Weekly);
if (lastBackup == null)
{
m_dataBackup.Backup(config, fileIndex + 1);
break;
}
Here is the Db Context class is as below
public class MonarchDbContext:DbContext,IMonarchDbContext
{
private IStoredProcedure m_storedProc;
private static object m_dbIntializerSet;
public MonarchDbContext(string nameOrConnectionString)
: base( nameOrConnectionString )
{
//-- Set the DB initializer only once.
System.Threading.LazyInitializer.EnsureInitialized( ref m_dbIntializerSet,()=>{
Database.SetInitializer<MonarchDbContext>(null);
//-- Give debug builds a chance to overwrite the above.
_SetInitializerForDebugBuilds();
return new object();
});
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
var csb = new SqlConnectionStringBuilder( this.Database.Connection.ConnectionString );
csb.MultipleActiveResultSets = true;
this.Database.Connection.ConnectionString = csb.ToString();
var objectContext = ( this as IObjectContextAdapter ).ObjectContext;
objectContext.CommandTimeout = 3600;
}
#region Public "Tables"
public IDbSet<DBBackupConfiguration> DBBackupConfigurations { get; set; }
public IDbSet<DBBackupHistory> DBBackupHistorys { get; set; }
public IStoredProcedure StoredProc
{
get
{
return System.Threading.LazyInitializer.EnsureInitialized(ref m_storedProc, () => new BackupStoredProc(this.Database));
}
}
#endregion
please let me know how can i solve the issue.
I found the issue
I need to add toList() at the end of the Linq code and it just worked for me.
public IEnumerable<DBBackupConfiguration> GetAllConfiguration()
{
var result = m_db.DBBackupConfigurations.Where(c => c.BackupStatus == 1).OrderByDescending(c => c.ConfigurationId).ToList();
return result;
}
Just add the List to Ienumerbale types

Adding new Item to CSLA EF4 pattern

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.

Simple update with Entity Framework

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();
}
}
}

Categories