Insert into multiple tables using WCF Transactions - c#

I am trying to add an entry into a table and use the primary key of that added entry to create an additional entry into another table.
The error I am getting is
The transaction manager has disabled its support for remote/network
transactions. (Exception from HRESULT: 0x8004D024)
I believe this is caused by creating multiple connections within a single TransactionScope, but I am doing everything within one context / using statement, so I do not believe that I should be receiving this error.
Service
[OperationBehavior(TransactionScopeRequired = true)]
public void CreateGroup(NewGroupData data)
{
var groupRepo = _GroupRepo ?? new InvestigatorGroupRepository();
groupRepo.CreateGroup(data.UserId, data.InvestigatorGroupName, data.HasGameAssignment, data.InstitutionId);
}
Repository
public void CreateGroup(string userId, string investigatorGroupName, bool hasGameAssignment, int institutionId)
{
using (var context = new GameDbContext())
{
var newGroup = new InvestigatorGroup()
{
InvestigatorGroupName = investigatorGroupName,
HasGameAssignment = hasGameAssignment,
InstitutionId = institutionId,
IsTrashed = false
};
int institutionUserId =
context.InstitutionUsers.Where(
iu => !iu.IsTrashed && iu.APUser.UserId == userId && iu.InstitutionId == institutionId).Select(iu => iu.InstitutionUserId).Single();
var newGroupUser = new InvestigatorGroupUser()
{
InstitutionUserId = institutionUserId,
InvestigatorGroup = newGroup,
CreationDate = DateTime.Now
};
context.InvestigatorGroupUsers.Add(newGroupUser);
context.SaveChanges();
}
}

You start with a wrong assumption.
The line...
int newGroupId = context.InvestigatorGroups.Add(newGroup).InvestigatorGroupId;
...will always assign 0 to newGroupId. The Add method only marks the entity for insert, but doesn't actually insert it. Only SaveChanges writes data to the database, not any other method in Entity Framework.
So the assignment...
InvestigatorGroupId = newGroupId,
...is faulty as well. You have to assign the new InvestigatorGroup to a navigation property in InvestigatorGroupUser:
InvestigatorGroup = newGroup,
Add this navigation property to InvestigatorGroupUser if you haven't got it yet.
If you have that, it's enough to execute these lines:
context.InvestigatorGroupUsers.Add(newGroupUser);
context.SaveChanges();
No need to Add the newGroup object too, It will be added by adding newGroupUser.
So if you do that, the only transaction you need is the one that SaveChanges uses internally by default. For the code you show, you don't need a TransactionScope. If this is part of a greater WCF transaction the story may be different, but I think at least you needed some misconceptions to be straightened out.

Related

insert multiple records one by one using LINQ

I'm trying to copy ProductStatisticsTemp table data to ProductStatistics table,
var str = from a in db.ProductStatisticsTemp select a;
ProductStatistics ls = new ProductStatistics();
foreach (var val in str.ToList())
{
ls.Product_ID = val.Product_ID;
ls.ProductNameEn = val.ProductNameEn;
ls.ProductNameAr = val.ProductNameAr;
db.ProductStatistics.Add(ls);
db.SaveChanges();
}
first record can insert but once its try to insert 2nd one getting following error
The property 'Product_ID' is part of the object's key information and
cannot be modified.
It's because you have one instance of an object and try to add already added object twice.
You need to create new object of ProductStatistics in the loop.
Also you can save changes just once after the loop to improve performance by trigger DB communication just once:
var str = from a in db.ProductStatisticsTemp select a;
foreach (var val in str.ToList())
{
ProductStatistics ls = new ProductStatistics
{
Product_ID = val.Product_ID,
ProductNameEn = val.ProductNameEn,
ProductNameAr = val.ProductNameAr
};
db.ProductStatistics.Add(ls);
}
db.SaveChanges();
Here is a slightly different method.
var products = db.ProductStatisticsTemp.Select(t => new ProductStatistics
{
Product_ID = t.Product_ID,
ProductNameEn = t.ProductNameEn,
ProductNameAr = t.ProductNameAr
}).ToList()
db.ProductStatistics.AddRange(products);
db.SaveChanges();
IMHO Inspired from #Vadim Martynov
If the Product_ID is your primary key, and your set to increment
the key from database . Do not do this Product_ID = val.Product_ID.
The key should be generated from the database. You will get the id
after save changes is invoked.
try
{
var str = from a in db.ProductStatisticsTemp select a;
//This will improve some performance
db.Configuration.AutoDetectChangesEnabled = false;
foreach (var val in str.ToList())
{
ProductStatistics ls = new ProductStatistics
{
Product_ID = val.Product_ID,
ProductNameEn = val.ProductNameEn,
ProductNameAr = val.ProductNameAr
};
//use AddRange or Add based on your EF Version.
db.ProductStatistics.Add(ls);
}
db.SaveChanges();
}
finally
{
db.Configuration.AutoDetectChangesEnabled = true;
}
If you are using AddRange you could omit db.Configuration.AutoDetectChangesEnabled = false
For more info about DetectChanges available here
AddRange() method only support from EF6 see documentation
db.ProductStatistics.AddRange(products);
What AddRange will do for you is
if AutoDetectChangesEnabled is set to true (which is the default), then DetectChanges will be called once before adding any entities and will not be called again.
This means that in some situations AddRange may perform significantly
better than calling Add multiple times would do.
Note that entities that are already in the context in some other state will have their state set to Added. AddRange is a no-op for entities that are already in the context in the Added state.

How can I edit or add to a particular field without pull the all object

How I can do just this ( a.myFavorits.Add()) without pulling the all object to var a , because a has a lot of data, and I don't want to pull all a object, but I can't find a way do do it.
I want to do the lambada and the linq without return something but linq is always return something
public static void addFavorits(long f,long idUser)
{
using (var db = dataBase())
{
// here i pull object user from users table
var a = db.users.Where(c => c.id == idUser).SingleOrDefault();
// here i adding to the object field myFavorits new value
//myFavorits is also a table of entitys that connected to user object
a.myFavorits.Add(new BE.FavoritsUsersLong { myLong = f });
db.SaveChanges();
}
}
I thought to do something like this but i dont know how to set the field users_TableId that is the key that connect the 2 tables
public static void addFavorits(long favoritId,long idUser)
{
using (var db = dataBase())
{
db.favoritsUsersLong.Add(new BE.FavoritsUsersLong {myLong = favoritId}
/*,users_TableId =idUser*/);
db.SaveChanges();
}
}
Here's a concrete example that does what you want. In this example, only the Name of a Company is modified and saved. Or an item is added to one of its collections.
var cmp = new Company{ CmpId = 1, Name = "Cmp1" }; // CmpId is the primary key
db.Companies.Attach(cmp);
db.Entry(cmp).Property(c => c.Name).IsModified = true;
// Or add an entity to a collection:
cmp.Users = new[] {new User { Name = "a1", PassWord = "a1" } };
try
{
db.Configuration.ValidateOnSaveEnabled = false;
db.SaveChanges();
}
finally
{
db.Configuration.ValidateOnSaveEnabled = true;
}
Result in SQL:
DECLARE #0 VarChar(30) = 'Cmp1'
DECLARE #1 Int = 1
UPDATE [dbo].[Company]
SET [Name] = #0
WHERE ([CmpId] = #1)
There are a few things to note here:
Obviously you need to know the Id of the entity you want to modify.
The object you create is called a stub entity, which is an incomplete entity. When you try to save such an entity, EF is very likely to complain about null values in required properties. That's why almost certain you'd have to disable validation (temporarily, or, better, dispose the context immediately).
If you want to add an item to a collection, you should leave validation enabled, because you'd want to know for sure that the new entity is valid. So you shouldn't mix these two ways to use a stub entity.
If you often need roughly the same small part of your entity you may consider table splitting.
I'm guessing this is what you want? I don't see you 'editting' I only see you adding.
using (var db = dataBase())
{
var a = new user();
....
//set properties etc..
...
a.myFavorits.Add(new BE.FavoritsUsersLong { myLong = f });
db.users.Add(a);
db.SaveChanges();
}

Linq to Sql General Help - Insert Statement

I am currently trying to create a new order (which will be shown below) in a web service, and then send that data to insert a new row into the database. For some reason my DBML / Data Context does not allow me to use InsertOnSubmit.
Any ideas? I haven't used Linq to Sql in about 7 months.
Thanks in advance.
[WebMethod]
public string InsertOrderToDatabases()
{
//Start Data Contexts ------
DataContext db = new DataContext(System.Configuration.ConfigurationManager.AppSettings["RainbowCMSConnectionString"]);
DataContext dcSqlOES = new DataContext(System.Configuration.ConfigurationManager.AppSettings["OESConnectionString"]);
//Get table from local database
Table<Schedule> Schedule = db.GetTable<Schedule>();
//Find last order number in databases
var lastOrderNumber = from lOrder in Schedule
orderby lOrder.templ_idn descending
select lOrder.templ_idn;
int firstOrderID;
var firstOrder = lastOrderNumber.FirstOrDefault();
firstOrderID = firstOrder.Value + 1;
qrOrder qrOrd = new qrOrder
{
.... data in here creating a new order
};
//TODO: fix below with an insert on submit
if (qrOrd != null)
{
// **Schedule.InsertOnSubmit(qrOrd);**
}
//db.GetTable<Schedule>().InsertOnSubmit(qrOrd);
try
{
//Submit the changes to the database
db.SubmitChanges();
return "Orders were sent to the databases.";
}
catch ()
{
}
}
Based on your response, it appears that you are using the wrong table, or perhaps the wrong data type. I also noticed that when you declare your localSchedule variable, you declare it as type Table<Schedule>, which means it should contain Schedule entities, not qrOrder entities.
Table<TEntity>.InsertOnSubmit expects a specific strongly typed entity to be passed in. In your case, it is expecting Web_Service.Schedul‌e, but you are trying to pass in a qrOrder.
Schedule.InsertOnSubmit(qrOrd);
That line will not treat to submit changes to connected entity , Try this
db.Schedule.InsertOnSubmit(qrOrd);
db.SubmitChanges();
you can try with
db.GetTable(typeof(Schedule)).InsertOnSubmit(qrOrd);
Or
db.GetTable(qrOrd.GetType()).InsertOnSubmit(qrOrd);

dbset.local updating database: a duplicate value cannot be inserted into a unique index

I am getting an error when calling entities.savechanges() on my EF 4.3.1. My database is a sql ce v4 store and I am coding in the mvvm pattern. I have a local version of my context that I send to an observable collection and modify etc. This works fine, and when I call savechanges() when no rows exist in the database the objects persist fine. When I reload the application, the objects are populated in my listbox as they should, however if I add another object and call savechanges() I get an error saying that a duplicate value cannot be inserted into a unique index.
From my understanding it means that the context is trying to save my entities to the datastore, but it seems to be adding my untouched original objects as well as the new one. I thought it would leave them alone, since their state is unchanged.
private void Load()
{
entities.Properties.Include("Images").Load();
PropertyList = new ObservableCollection<Property>();
PropertyList = entities.Properties.Local;
//Sort the list (based on previous session stored in database)
var sortList = PropertyList.OrderBy(x => x.Sort).ToList();
PropertyList.Clear();
sortList.ForEach(PropertyList.Add);
propertyView = CollectionViewSource.GetDefaultView(PropertyList);
if (propertyView != null) propertyView.CurrentChanged += new System.EventHandler(propertyView_CurrentChanged);
private void NewProperty()
{
try
{
if (PropertyList != null)
{
Property p = new Property()
{
ID = Guid.NewGuid(),
AgentName = "Firstname Lastname",
Address = "00 Blank Street",
AuctioneerName = "Firstname Lastname",
SaleTitle = "Insert a sales title",
Price = 0,
NextBid = 0,
CurrentImage = null,
Status = "Auction Pending",
QuadVis = false,
StatVis = false, //Pause button visibility
Sort = PropertyList.Count + 1,
};
PropertyList.Add(p);
SaveProperties();
}
private void SaveProperties()
{
try
{
foreach (var image in entities.Images.Local.ToList())
{
if (image.Property == null)
entities.Images.Remove(image);
}
}
catch (Exception ex)
{
System.Windows.MessageBox.Show(ex.Message);
}
entities.SaveChanges();
}
Without commenting on all the code here this is the bit that's causing the specific problem you bring up:
//Sort the list (based on previous session stored in database)
var sortList = PropertyList.OrderBy(x => x.Sort).ToList();
PropertyList.Clear();
sortList.ForEach(PropertyList.Add);
This code:
Starts with entities that have been queried and are being tracked by the context as Unchanged entities. That is, entities that are known to already exist in the database.
Creates a new sorted list of these entities.
Calls Clear on the local collection causing each tracked entity to be marked as deleted and removed from the collection.
Adds each entity back to the context putting it now in an Added state meaning that it is new and will be saved to the database when SaveChanges is called,
So effectively you have told EF that all the entities that exist in the database actually don't exist and need to be saved. So it tries to do this and it results in the exception you see.
To fix this don't clear the DbContext local collection and add entities back. Instead you should sort in the view using the local collection to back the view.
It sounds like you're adding the existing entities to the context (which marks them for insertion) instead of attaching them (which marks them as existing, unmodified).
I'm also not sure that new Guid() isn't returning the same guid... I always use Guid.NewGuid() http://msdn.microsoft.com/en-us/library/system.guid.newguid.aspx

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