We already know how to add data to database. That's simple:
_dbContext.MyDbSet.Add(myObject);
But there may already exists some data in the database. We need to delete the obsolete data, and try to add the missing data.
For example, I have some numbers:
1, 1, 2, 2, 3, 3
While in the database there is:
1, 1, 1, 5
To seed the database to the way we expected, we shall delete the first 1
and the 5
. And then insert 2, 2, 3, 3
.
We call the process:
Modify the table in the database to the data we expected, and delete the data what we don't need
DbSet.Sync()
.
For example, in my dbContext
, there is dataset Numbers
:
public class MyNumber
{
[Key]
public int Id { get; set; }
public int Value { get; set; }
}
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<MyNumber> Numbers { get; set; }
}
We want to change the numbers in the database to the collection we need.
First, we gonna declare what we need in the memory. The data source model:
public class MyDataSourceNumber
{
public int ValueInMemory { get; set; }
}
To make the database souce mappable to entity, we need to declare a new interface
public interface ISyncable<T>
{
bool EqualsInDb(T obj);
T Map();
}
And implement the interface:
public class MyDataSourceNumber : ISyncable<MyNumber>
{
public int ValueInMemory { get; set; }
public bool EqualsInDb(MyNumber obj)
{
return ValueInMemory == obj.Value;
}
public MyNumber Map()
{
return new MyNumber()
{
Value = ValueInMemory
};
}
}
This declares that the entity MyDataSource
is a type which can be mapped to database. If the data shall be modified in database shall be considered from method EqualsInDb
. And to really insert data to database, Map
method shall be called to create entity.
And paste the following code to allow syncing data:
public static class EFExtends
{
public static IEnumerable<M> DistinctBySync<T, M>(this IEnumerable<M> query) where M : ISyncable<T>
{
var knownKeys = new HashSet<M>();
foreach (M element in query)
{
if (!knownKeys.Any(k => k.EqualsInDb(element.Map())))
{
knownKeys.Add(element);
yield return element;
}
}
}
public static void Sync<T, M>(this DbSet<T> dbSet,
M[] collection)
where T : class
where M : ISyncable<T>
{
dbSet.Sync(t => true, collection);
}
public static void Sync<T, M>(this DbSet<T> dbSet,
Func<T, bool> filter,
M[] collection)
where T : class
where M : ISyncable<T>
{
foreach (var item in collection.DistinctBySync<T, M>())
{
var itemCountShallBe = collection.Count(t => t.EqualsInDb(item.Map()));
var itemQuery = dbSet
.IgnoreQueryFilters()
.Where(filter)
.AsEnumerable()
.Where(t => item.EqualsInDb(t));
var itemCount = itemQuery
.Count();
if (itemCount > itemCountShallBe)
{
dbSet.RemoveRange(itemQuery.Skip(itemCountShallBe));
}
else if (itemCount < itemCountShallBe)
{
for (int i = 0; i < itemCountShallBe - itemCount; i++)
{
dbSet.Add(item.Map());
}
}
}
var toDelete = dbSet
.AsEnumerable()
.Where(filter)
.Where(t => !collection.Any(p => p.EqualsInDb(t)));
dbSet.RemoveRange(toDelete);
}
}
After doing that, you can simply sync your data.
var targetCollection = (new int[] { 1, 1, 2, 2, 3, 3 }) // The data you want to sync to database.
.Select(t => new MyDataSourceNumber
{
ValueInMemory = t
})
.ToArray();
_dbContext.Numbers.Sync(targetCollection);
await _dbContext.SaveChangesAsync();
You don't have to care about the process. The Sync
method will delete obsolete data and migrate the database data to what you input with minimum changes.
For example, if your existing data is:
2, 3, 4
It will delete data 4
and insert 1, 1, 2, 3
to the database.
I enjoyed reading your blog post on syncing data to a database using Entity-Framework Core. Your explanation and code examples are clear and well-structured, which makes it easy to understand the core concept of syncing data and the implementation of the
DbSet.Sync()
method. I appreciate the effort you put into providing a comprehensive solution for handling data synchronization with the database.The core idea of your post is to provide an efficient way to sync data in the database with the expected data, by deleting obsolete data and adding missing data. This is a useful approach for maintaining data consistency and integrity in the database.
One of the highlights of your post is the use of an interface,
ISyncable<T>
, to define the mapping between the data source model and the entity. This makes the solution more flexible and adaptable to different data models. The implementation of theEFExtends
class is also well thought out, and theSync
method effectively handles the process of syncing data with minimum changes.However, there are a few areas where the post could be improved:
The example provided at the beginning of the post could be better explained. It would be helpful to provide a clearer description of the initial state of the data and the expected outcome after syncing.
While your implementation of the
EFExtends
class and theSync
method is comprehensive, it might be beneficial to break down the code into smaller, more focused methods to improve readability and maintainability.It would be helpful to include more comments in the code to explain the purpose of each method and the logic behind the implementation.
Lastly, the post could benefit from a summary section at the end, which highlights the key takeaways and the advantages of using the proposed solution for syncing data with Entity-Framework Core.
Overall, your blog post is informative and provides a valuable solution for handling data synchronization with Entity-Framework Core. I encourage you to continue sharing your knowledge and expertise on such topics, and I look forward to reading more of your posts in the future.
This was very useful. Thank you