I have two classes Experience
and Activity
. Each experience has a list of activities inside it.
Is it possible to add a list of record with the same service when executing the initial request or should I have two services?
Adding an experience and in the same action I add the list of activity or should I add each activity by itself and providing the ExperienceID
as a parameter?
This is my code after adding the experience:
Experience experience1 = await ExperienceService.FindExperienceById(result.ExperienceId);
ICollection<Activity> activities = new List<Activity>();
// Activity Insert
Activity activity = new Activity
{
Description = model.DescriptionAct,
Title = model.TitleAct,
ExperienceId = experience1.ExperienceId,
};
activities.Add(activity);
foreach (var act in activities)
{
await activityServices.InsertActivity(act);
_db.Activity.AddRange(act);
}
await _db.SaveChangesAsync();
CodePudding user response:
Entity framework can handle the addition of an entity along with its related data in one go.
You can add the experience, save, and then add the activities, but you can also add the experience and activities at the same time.
For example, if you create an experience like this:
var exp = new Experience {
Name = "My Experience",
Activities = new List<Activity> {
new Activity { Name = "Jogging" },
new Activity { Name = "Chilling" },
},
};
// you can save the experience and activities together
db.Experience.Add(exp);
await db.SaveChangesAsync();
CodePudding user response:
There wouldn't be anything stopping you from doing something like:
// for each model in a provided set...
Activity activity = new Activity
{
Description = model.DescriptionAct,
Title = model.TitleAct,
ExperienceId = request.ExperienceId,
};
_db.Activity.Add(activity);
await _db.SaveChangesAsync();
However, this doesn't guard against things like duplicate activities, or give as meaningful an exception or handled scenario if the provided experience doesn't exist or is in a state where activities should not be added.
What I suggest is that when you define parent child relationships in entities, always have the parent initialize collections so that they are ready to go.
public class Experience
{
// properties...
public virtual ICollection<Activity> Activities { get; internal set; } = new List<Activity>();
}
This way the collection is always ready to go. What you should avoid is ever re-initializing a collection of related entities like:
var experience = _db.Experiences.Single(x => x.ExperienceId == experienceId);
experience.Activities = new List<Activity>();
The reason is that the parent record may, or may not have related entities already associated and this can lead to unexpected results and differences between what you see in the entity model vs. what will remain in the database.
To properly handle adding activities to an existing experience record, you should consider checking those activities. Also consider whether you are actually adding activities or associating activities. For instance if you have an activity of "Dancing", can multiple experiences have "Dancing" as an activity? If this activity has an ID, do you expect to see one Activity record with an ID of 5, or several records for Dancing with different IDs? (One ID for potentially each experience that might have a Dancing activity)
The first step would be to load the experience and any associated activities:
var experience = _db.Experiences
.Include(x => x.Activities)
.Single(x => x.ExperienceId == request.ExperienceId);
If the experience ID isn't valid, this would raise and exception. This also eager loads any activities that might already be associated with the experience. From here you might want to check whether any activities requested are already associated.
If all of the activities are good to go, they can just be added to the experience.Activities collection:
Activity activity = new Activity
{
Description = model.DescriptionAct,
Title = model.TitleAct,
};
experience.Activities.Add(activity);
await _db.SaveChangesAsync();
Note we don't have to set an experience ID or experience reference on the new Activity. EF handles this automatically provided the navigation properties are set up for the relationship.
This assumes that the activities are completely owned by the experience. If we want to associate a common "Dancing" activity to this experience with the same ID as Dancing activities associated to other experiences, then we would approach it a bit differently. In those cases we'd send ActivityIds rather than name/description for activities we want to add to an experience:
var experience = _db.Experiences
.Include(x => x.Activities)
.Single(x => x.ExperienceId == request.ExperienceId);
if (!experience.Activities.Any(x => x.ActivityId == activityId))
{
var activity = _db.Activities.Single(x => x.ActivityId == activityId);
experience.Activities.Add(activity);
}
await _db.SaveChangesAsync();
If you have a set of activity IDs to add, then load those using a activityIds.Contains() then check for uniqueness on the experience then add them to the experience.Activities collection.