EFCore.BulkExtensions - BulkUpdate using pimarykey - c#

We are trying to BulkUpdate(EFCore.BulkExtensions) a table based on primary key. We need to update ONLY Name based on Id and not Age
Model
public class Student
{
public int Id { get; set; } // Primary Key
public string Name { get; set; }
public int Age { get; set; }
}
Here is the code I used to try to update a student's name using primary key Id
List<Student> students = new List<Student>();
students.Add(new Student()
{
Id = 1,
Name = "Name 1",
Age = 25
});
var updateByProperties = new List<string> { nameof(Student.Id) };
var propToExclude = new List<string> { nameof(Student.Id) };
var bulkConfig = new BulkConfig { UpdateByProperties = updateByProperties, PropertiesToExclude = propToExclude };
_dbContext().BulkUpdate(students, bulkConfig);
My expectation here is it will update column Name of a row which has Id as 1 but I am getting the following error
The given key 'Id' was not present in the dictionary.
So how do I BulkUpdate(EFCore.BulkExtensions) a table based on primary key.

Let's suppose that Id is a primary key (PK) and you need to update ONLY Name based on Id and not Age.
It is needed to set PropertiesToInclude attribute within BulkConfig. Attention: you do not have to use PropertiesToInclude and PropertiesToExclude at the same time. (If you want to include more than half attributes, it is better to use PropertiesToExclude, but in your case you want to change only Name, so we will use the attribute Include).
Also, it is not needed to define UpdateByProperties, because you want to update data by Id (which is PK). UpdateByProperties define attributes which are used as lookup for updating.
So the code should be like that:
List<Student> students = new List<Student>();
students.Add(new Student()
{
Id = 1,
Name = "Name 1",
Age = 25
});
var propToInclude = new List<string> { nameof(Student.Name) };
var bulkConfig = new BulkConfig { PropertiesToInclude = propToInclude };
_dbContext().BulkUpdate(students, bulkConfig);

Related

How to increment an integer property for every class inside List in c#

I am having a class as below:
public class DBEmployee
{
public Guid EmpId {get;set;}
public string Name {get;set;}
public int IncrementNo {get;set;}
}
public class C#Employee
{
public Guid EmpId {get;set;}
public string Name {get;set;}
public int IncrementNo {get;set;}
}
Consider I got List of Employees from database and bind it to the variable listOfEmployees as below:
var listOfEmployees = new List<DBEmployee>
{
new DBEmployee {
EmmId = "e31d712a-7d5c-4b1c-99a8-306f9aebbfa0",
Name = "AAA",
IncrementNo = 0
},
new DBEmployee {
EmmId = "87c4feed-20e7-42b2-8a95-8acb0743fdfe",
Name = "BBB",
IncrementNo = 0
},
new DBEmployee {
EmmId = "90cdd219-8796-4d51-bfe1-add8515315c5",
Name = "CCC",
IncrementNo = 0
},
}
Now I am using AutoMapper to map DBEmployee to C#Employee as below:
var config = new MapperConfiguration(cfg => cfg.CreateMap<DBEmployee, C#Employee>()
.AfterMap((src, dest) => dest.IncrementNo= dest.IncrementNo+ 1)
);
var mapper = new Mapper(config);
var c#Emps = mapper.Map<List<C#Employee>>(listOfEmployees);
I tried in above format but it's always getting IncrementNo as 1 for every Employee.
Now I want the IncrementNo property to be increment by +1 for every employee object such as
Employee Name AAA should contain IncrementNo=1 and Employee Name BBB should contain IncrementNo=1 and Employee Name CCC should contain IncrementNo=3.
I can acheieve this by using Foreach loop as below:
var lR = 0;
c#Emps.ForEach(x =>
{
x.IncrementNo= lR + 1;
lR++;
});
but i just want to try in a better way mostly while configuring AutoMapper itself rather that having more lines of code using ForEach
Could someone help me with this?
Have you tried this?
Mapper.CreateMap<DBEmployee, C#Employee>()
.AfterMap((src, dest) =>
{
int incrementNo = 1;
foreach (var employee in listOfEmployees)
{
employee.IncrementNo = incrementNo++;
}
});
Sorry please ignore typing mistakes as I have not compiled the code. But conceptually it should be similar.

How to update foreign key in EF 6 - Code First

I'm trying to update a foreign key in EF6 (Code First) in an ASP.Net MVC manner.
Let me explain :
My entities
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Country Country { get; set; }
}
public class Country
{
public int Id { get; set; }
public string Name { get; set; }
}
My database
Table Countries with 2 records :
Id = 1, Name = France
Id = 2, Name = Canada
Table People with 1 record :
Id = 1, Name = Nicolas, Country_Id = 1
My code
// In a MVC application, these class has been retrieved via EF in a previous page. So now, we've lost all "proxy" informations
var p = new Person() { Id = 1, Name = "Nicolas" };
// Change country
p.Country = new Country() { Id = 2, Name = "Canada" };
// Persist all in DB
using (var db = new TestDbContext())
{
db.Persons.Attach(p); // Reattach to EF context
db.Entry<Person>(p).State = EntityState.Modified; // Flag modified state
db.SaveChanges(); // Generate only modification on field "name"
}
My issue
When the previous code is executed, the generated SQL never include the country_Id field from the person table.
My "not" issue
I know that it works perfectly when doing all these lines of codes in one EF context but in my case, I will have the data coming from my ASP.Net MVC page.
I would also like to avoid to retrieve the existing data and modify each field one by one
By retrying #ivan solution, I was first able to do what was wanted.
Here are the modifications done :
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Country Country { get; set; }
public int Country_Id { get; set; }
}
// ...
// Change country
p.Country = new Country() { Id = 2, Name = "Canada" };
p.Country_Id = 2;
But now, I get an exception when getting entities from the database.
Using this code :
// Retrieve data first
using (var db = new TestDbContext())
{
var p2 = db.Persons.First();
}
I get the following SqlException : "Invalid column name 'Country_Id1'."
Does anyone have any clues to be able to retrieve data and to update the foreign key ?

Entity Framework 6 update a table and insert into foreign key related tables

Not quiet sure how to word the title for this one so feel free to edit if it isn't accurate.
Using an example, what I am trying to accomplish is update a record in table foo, and then create new records in a subsequent table that has the foo tables PK as foreign key, think One-to-Many relationship.
How do I update a table that has foreign key constraints and create a new related record(s) in these subsequent table(s)?
Currently I am using Entity Framework 6 to .Add and .Attach entities to the context and save them to the database.
Edit
To clarify further what I am trying to achieve, the below object is a cut down made up example what I am trying to save to the context. If I try to .Add intObj after "Billy Bob" has already been created because he has bought a new car, another service, or his tyres have changed it will create a new Billy Bob record (duplicate) and the corresponding related tables.
intObj.FirstName = "Billy";
intObj.Lastname = "Bob";
intObj.Important = 100;
intObj.LastSeen = DateTime.Now.Date;
intObj.Cars = new List<Car>{
new Car{
Model = "Commodore",
Make = "Holden",
YearMade = DateTime.Today.Date,
Odometer = 15000,
EmailWordCount = 500,
TyreStatuss = new List<TyreStatus>{
new TyreStatus{
Tyre1 = "Good",
Tyre2 = "Good",
Tyre3 = "Okay",
Tyre4 = "Okay"
}
},
Services = new List<Service>{
new Service{
Cost = "$500",
Time = "2 Days",
Date = DateTime.Today
}
},
}
};
Thanks
In the following snippets you have Employee class, which references two more entities: a collection of Assignment and a single Country.
ORMs like EF , NHibernate, etc... have a feature known as Transitive Persistence, that is, if an object (Assignment and Country) is referenced by a persistent one (Employee), then Assignments and Country will eventually become persistent too when, in your EF case, SaveChanges method gets invoked in the Context, without you explicitly save them.
public class Employee
{
public virtual Guid Id { get; protected set; }
public virtual string EmployeeNumber { get; set; }
public virtual Country BirthCountry { get; set; }
private ICollection<Assignment> _assignment = new List<Assignment>();
public virtual ICollection<Assignment> Assignments
{
get
{
return _assignment;
}
set
{
_assignment= value;
}
}
}
public class Assignment
{
public virtual Guid Id { get; protected set; }
public virtual DateTime BeginTime { get; set; }
public virtual DateTime EndTime { get; set; }
public virtual string Description{ get; set; }
}
public class Country
{
public virtual Guid Id { get; protected set; }
public virtual string Name { get; set; }
}
//Somewhere in your program
private void SaveAllChanges()
{
_db = new EFContext();
//Creating a new employee here, but it can be one loaded from db
var emp = new Employee { FirstName = "Emp Name",
LastName = "Emp Last", EmployeeNumber = "XO1500"
};
emp.BirthCountry = new Country { Name = "Country1" };
emp.Assignment.Add(new Assignment{ BeginTime = DateTime.Now,EndTime=DateTime.Now.AddHours(1) });
//Only employee is explicitly added to the context
_db.Employees.Add(emp);
//All the objects in the employee graph will be saved (inserted in this case) in the db.
_db.SaveChanges();
}
}
EDIT:
That is very similar to my code above, once "Billy Bob" is created you only need to update it, and that include any new service he buy;
Pseudo code:
var bob = _db.Clients.SingleOrDefault(c=> c.Id = "Bob Row Id")
//Bob buy a car:
bob.Cars.Add(new Car()...)
//...and change tire 1 from an old car
var car = bob.Cars.SingleOrDefault(c=> c.Id = "Car Row Id")
car.TireStatus.Tire1 = "New"
....
//Persist all changes
//Existing objects will be updated..., and the new ones created in this process will be inserted
_db.SaveChanges()
Let me know if this clarify your ideas

Getting repeated data from a CSV file

I have a person class like so:
class Person
{
string Id { get; set; }
string FirstName { get; set; }
string LastName { get; set; }
}
There is a CSV file which has person data like
"123","ABC","DEF"
"456","GHI","JKL"
"123","MNO","PQR"
...
A person is unique based on the Id.
The CSV is read like this:
using (StreamReader sr = new StreamReader(inputFile))
{
string[] arrCsvData;
string strLine;
while ((strLine = sr.ReadLine()) != null)
{
arrCsvData = strLine.Split(',');
this.LoadPersonData(arrCsvData);
}
}
In LoadPersonData a new Person object is created and assigned the values from CSV:
Person objPerson = new Person();
for (int i = 1; i <= arrCsvData.Length - 1; i++)
{
// Assign person property values from arrCsvData
}
I have a dictionary object in which the key is the ID and the value is the Person object.
if(!this.PersonDataCollection.ContainsKey(personKey))
{
this.PersonDataCollection.Add(objPerson);
}
This gives me all the unique Person objects from the CSV file.
I want to create a list of those Person objects which are repeated based on Id in the CSV.
So the list DuplicatePersons will have:
"123","ABC","DEF"
"123","MNO","PQR"
in it.
The bare bones way is to first read all the person objects into a list and then do a LINQ query to get all the duplicates in a separate list. This way I have to create an extra collection just to get the duplicates.
There should be a better way than creating a separate list.
Any pointers?
First of all, I would use LINQToCSV. Parsing CSV files is more complicated than just splitting by ,. You don't need to code anything, just create your class, and place attributes on it:
class Person
{
[CsvColumn(Name = "ID", ...)]
string Id { get; set; }
[CsvColumn(Name = "First Name", ...)]
string FirstName { get; set; }
[CsvColumn(Name = "Last Name", ...)]
string LastName { get; set; }
}
Then when you read the file using LINQToCSV, you get an IEnumerable<Person>... and then you can do:
IEnumerable<Person> people = ... //read here using LINQToCSV
var grouped = people.GroupBy(p => p.Id);
If you will know the the unique column at runtime, you can do something like this:
string columnName = "Id";
persons.GroupBy(x => x.GetType().GetProperty(columnName).GetValue(x, null));
although you will have to see how much it affects you in performance. Another way that doesn't require Reflection could be:
Dictionary<string, Func<Person, object>> selectors = new Dictionary <string, Func<Person, object>>
{
{"Id", x => x.Id},
{"FirstName", x => x.FirstName},
{"LastName", x => x.LastName},
};
string columnName = "Id";
var grouped = people.GroupBy(selectors[columnName]);
Now, using your approach... what's wrong with creating another dictionary?
You could have something like:
//Here you will store the duplicated person
//Key: The person Id
//Value: The list of person sharing that same Id
Dictionary<string, IList<Person>> duplicatedPeople;
if(!this.PersonDataCollection.ContainsKey(personKey))
{
this.PersonDataCollection.Add(objPerson);
}
else
{
//Here we store all the people with this already existing ID
IList<Person> duplicatedPeople;
//If we already have this ID in the dictionary of repeated people
if (this.duplicatedPeople.TryGetValue(personKey, out duplicatedPeople)) {
//Just add this new person
duplicatedPeople.Add(objPerson);
}
//If this is the 1st time we found a duplicated person with this ID
else {
//We add two persons to the list: this one, and the one from PersonDataCollection.
duplicatedPeople = new List<Person> { this.PersonDataCollection[personKey], objPerson };
//Add it to the dictionary
this.duplicatedPeople.Add(personKey, duplicatedPeople);
}
}
Why Don't you check whether the values are already exist at this point.
Person objPerson = new Person();
for (int i = 1; i <= arrCsvData.Length - 1; i++)
{
// Assign person property values from arrCsvData
}
Check your condition here and do what ever you want with the duplicate values at that point.
Whatever you do.. there will always be a separate list. It is up to you on how you want them to come about though.
Option 1 - Temporary lists
Each time you query your existing dictionary, an in-memory result will be returned. Depending on how big your dataset is.. this may not be what you're after.
Option 2 - Static list
Why not just maintain your own list at this point?:
if(!this.PersonDataCollection.ContainsKey(personKey))
{
this.PersonDataCollection.Add(objPerson);
}
else
{
// Create a new dictionary for the duplicates
this.DuplicatePersonDataCollection.Add(objPerson);
}
Create a single list for all the persons and rather query it with LINQ to get your results:
ie:
var persons = new List<Person>();
persons.Add(new Person { Id = "123", FirstName = "AAA", LastName = "XXX" });
persons.Add(new Person { Id = "123", FirstName = "BBB", LastName = "WWW" });
persons.Add(new Person { Id = "456", FirstName = "CCC", LastName = "XXX" });
persons.Add(new Person { Id = "456", FirstName = "DDD", LastName = "YYY" });
persons.Add(new Person { Id = "789", FirstName = "EEE", LastName = "ZZZ" });
var duplicateKeys = persons.GroupBy(p => p.Id).Select(g => new { g.Key, Count = g.Count() }).Where(x => x.Count > 1).ToList().Select(d => d.Key);
var duplicatePersons = persons.Where(p => duplicateKeys.Contains(p.Id)).ToList();
var unique = persons.GroupBy(p => p.Id).ToList();

Updating list's element's properties

I have a class
class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int ID { get; set; }
}
Now, I have created a List of Student object as:
List<Student> lst = new List<Student>();
Three properties of the class will come from three different Data Sources. So I cannot add all properties of object at a time. I am adding the ID property as following:
lst.Add(new Student { ID = 1 }, new Student { ID = 2 })
Now I want to set the values of the "FirstName" or "LastName" property in the existing list. How I can do this?
List<Student> lst = new List<Student>();
lst.Add(new Student { ID = 1 });
lst.Add(new Student { ID = 2 });
//Get the student you want by id then use that to populate the remaining properties
var temp = lst.Single(l => l.ID == 1);
temp.FirstName = "fname";
temp.LastName = "lastname";
Don't mix method Add() call:
list.Add(new Student());
and collection initialization syntax:
List<Student> list = new List<Student>
{
new Student(),
new Student()
};
Then
int index = 1;
Student student = list[index]; // may cause IndexOutOfRangeException
student.Name = "Me"; // may cause NullReferenceException
or shorter:
list[i].Name = "Me"; // may cause them both too
You can get the index from an element that matches with the element to update:
int index= MyObjectList.FindIndex(x=>x.Id==object.Id);
and after updating it:
MyObjectList[index]=object;
Im only guessing here... if , doesnt work try ;
lst.Add(new Student{ID=2, FirstName="The", LastName="Stig"});
Or you can define a constructor for the Student object...
public Student(int mID, string mFirstName, string mLastName)
{
ID = mID;
FirstName = mFirstName;
LastName = mLastName;
}
Then use
lst.Add(new Student(2, "The", "Stig"));
Assuming the other data sources contains the studentId and all students has a last and first name.
Join the lastNameList and firstNameList collections using linq. The studentId list you dont need since the other two has that value also.
var list =
(from lastName in lastNameList
join firstName in firstNameList on lastName.studentId equals firstName.studentId
select new Student()
{
.ID = lastName.studentId,
.FirstName = firstName.Name,
.LastName = lastName.Name
}).ToList();
You can use list.count-1 to get the last item in the list. Then you can update the properties you want.
List<Student> lst=new List<Student>();
lst.Add(new Student { ID = 1, FirstName = "FirstName", LastName = "LastName" });
//Then get props from other datasource
lst[lst.Count-1].FirstName="FirstName";
lst[lst.Count-1].LastName="LastName";

Categories