LINQ to SQL Insert Multiple Tables Question - c#

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

Related

Entity Framework insert sublist into parent sublist

I try to insert a list of Answers into Questions which in return it is inserted into Exams, all my code works just fine except one part, which is inserting new Answers.
Data is inserted just fine except Answers' data, whose data is not stored in the database, plus I try to get QuestionId so I can store it with Answer as a foreign key and I failed in that too.
API Controller
public IActionResult addExam([FromBody] Exams exam)
{
try
{
if (exam == null)
{
return StatusCode(401, "data is null");
}
var userId = this.help.GetCurrentUser(HttpContext);
Exams exams = new Exams
{
Name = exam.Name,
Number = exam.Number,
FullMarck = exam.FullMarck,
CreatedBy = userId,
CreatedOn = DateTime.Now,
Status = exam.Status
};
db.Exams.Add(exams);
var questionsList = new List<Questions>();
foreach (Questions item in exam.Questions)
{
var question = new Questions
{
ExamId = exam.Id,
Points = item.Points,
CreatedBy = userId,
CreatedOn = DateTime.Now,
Status = item.Status,
};
questionsList.Add(question);
}
exams.Questions = questionsList;
db.SaveChanges();
foreach (Questions item in exam.Questions)
{
var answersList = new List<Answers>();
foreach (Answers answers in item.Answers)
answersList.Add(new Answers
{
QuestionId = item.Id,
ExamAnswers = answers.ExamAnswers,
CreatedBy = userId,
CreatedOn = DateTime.Now
});
item.Answers = answersList;
}
db.SaveChanges();
return Ok("successfully created ");
}
catch (Exception e)
{
return StatusCode(500, e.InnerException.Message);
}
}
The way you build your graph is a bit unusual. I would have expected it more like a single set of nested loops that takes your supplied model and populates entity collections without forced ids. EF will track the IDs; you don't need to worry about them; when you add a new Answer to a particular question.Answers collection, you don't need to tell the Answer what it's QuestionID is; EF knows based on which question it was added to. If the ID for a question is not yet set, because it is generated by the db and no save has occurred, then saving the question will generate an ID and EF will ripple the change out to all the owned Answers in the question.Answers; you don't need to micro manage it
Here's a pseudo code of how I would expect it to go:
//model is an ExamModel
Exam e = new Exam(); //exam is a db entity
e.Title = model.ExamTitle; //model is not a db entity
foreach(QuestionModel mq in model.Questions){ //enumerate all the questionmodel we got from the front end and build a db entity graph
Question q = new Question(); //make new db entity
q.Subject = mq.QuestionHeader; //set entity property from model
q.Body = mq.BodyText; //set property from model
if(e.Questions == null) //not sure how your entities are declared, if this is already done elsewhere, remove it
e.Questions = new List<Question>();
e.Questions.Add(q); //add the question db entity to the exam db entity
//notice I didn't set the question id. EF will do that- it knows what exam this question belongs to
foreach(AnswerModel ma in mq.Answers){ //while our question db entity called q is still in scope let us add the related answers to it
Answer a = new Answer(); //create EF entity
a.Text = ma.AnswerText; //set db entity property from model property
if(q.Answers == null)
q.Answers = new List<Answer>();
q.Answers.Add(a); //add the answer to the question db entity
}
}
//exam e now has a collection of questions that have each a collection of answers, save it to the db
db.Exams.Add(e);
db.SaveChanges(); //only need one call to save changes on the whole graph
I think the way you've split your operations up hasn't created a connected graph of entities and/or the way you've forced the questionids of answers means EF hasn't kept the relationship to date upon saving.
You should also have a separation between the data objects arriving in your controller (I've called these ModelExam, ModelQuestion, ModelAnswer) and the entities in your EF (I've called these Exam, Question, Answer - yours are plural). This separation is achieved by having different classes for your front end controllers etc to use than your back end db context uses. At first it looks like things are being repeated for no good reason but eventually the system will become complex enough that not every db property can or should be exposed all the way to the front end and back, and the front end might need calculated or other non db based data. At this point you really need your front end data models to be completely separate things from your back end data entities
I think you need to call db.SaveChanges() after adding new exam. In your case exam id is not auto generated and it is always 0 , so you cant save question with examid 0;
item.Answers = answersList; is false
exam.Questions.Answers = answersList is true
Remove the first db.SaveChanges(); witch is before last foreach loop. This will insert all your data at once and should do the job.

Insert into multiple tables using WCF Transactions

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.

How to update many to many in Entity Framework with AutoDetectChangesEnabled = false

Please, help me to handle this situation:
I meaningly switched off AutoDetectChangesEnabled and I load my
entities AsNoTracked() meaningly either.
And I can't update many-to-many relationship in this case:
Here is the code of Update method:
public void Update(User user)
{
var userRoleIds = user.Roles.Select(x => x.Id);
var updated = _users.Find(user.Id);
if (updated == null)
{
throw new InvalidOperationException("Can't update user that doesn't exists in database");
}
updated.Name = user.Name;
updated.LastName = user.LastName;
updated.Login = user.Login;
updated.Password = user.Password;
updated.State = user.State;
var newRoles = _roles.Where(r => userRoleIds.Contains(r.Id)).ToList();
updated.Roles.Clear();
foreach (var newRole in newRoles)
{
updated.Roles.Add(newRole);
}
_context.Entry(updated).State = EntityState.Modified;
}
All simple fields, like Name, LastName updated. But the set
of Roles for User doesn't get updated - it stays the same.
I tried loading Roles using
_context.Entry(updated).Collection("Roles").Load();
But I can't update this loaded set in any way.
I searched for similar items but failed to find the answer, thought it definitely already exists.
I'm really sorry for possible dublicate.
PS. I want to add that I don't want to delete or update child entities at all.
A lot of existing answers suggest manually delete / add child entities to database in whole, but it is not suitable for me.
Roles are independent entities, any other user can use them.
I just want to update User_Role table in database, but I can't.

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

Editing an object in a list<objects> without creating a new instance of that object?

I'm making a database in EF4.1 Code First. I have a table, MedicalPlan, with a one-to-many relationship to a CoverageLevel. CoverageLevel primary key is incrementing. When I create the MedicalPlan I declare the coveragelevels and it creates those tables, like so:
medicalPlan.CoverageLevels = new List<CoverageLevel>();
medicalPlan.CoverageLevels.Add(new CoverageLevel() { Cost = 1200, Description = "single" });
medicalPlan.CoverageLevels.Add(new CoverageLevel() { Cost = 1500, Description = "spouse" });
medicalPlan.CoverageLevels.Add(new CoverageLevel() { Cost = 1100, Description = "family" });
I also have an update function in which I would update a medical plan. I would also like the functionality to update the MedicalPlan's CoverageLevels. In pseudocode, something like:
in medicalPlan edit first item in CoverageLevels() { Cost = 1500 };
The kicker is that I don't actually want to replace the CoverageLevel, as it has a unique auto-incrementing primary key, so if I create a new one it will have a different primary key than the original. Is there a way to do this in the way that I am attempting?
You can mutate entities or related entities in an EF context, and saving changes should properly handle all the updates for you without needing to create new entries:
// get EF Context
var firstCoverage = myMedicalPlan.CoverageLevels.FirstOrDefault();
if (firstCoverage != null) firstCoverage.Cost = 1500;
// save changes
Once you have loaded a MedicalPlan from the database you should be able to work with it and any related entities using Linq or accessing directly via the property on MedicalPlan as if it were a normal collection of .NET objects.
Calling Save would persist back to the database.
E.g.
var medicalPlan = GetMedicalPlanFromDataContext(); //example method
medicalPlan.CoverageLevels.First().Cost = 1500;
//OR
medicalPlan.CoverageLevels[0].Cost = 1500
medicalPlan.Save();
Or probably more likely..
var medicalPlan = GetMedicalPlanFromDataContext();
var coverage = medicalPlan.CoverageLevels.Where(x=>x.Description == "family").Single();
coverage.Cost = 1500;
medicalPlan.Save();

Categories

Resources