Solving Real Life Scenarios With .Net Core Entity Framework

Bora Kaşmer
Sep 4, 2020 · 7 min read
Image Source: Australianphotography

Today we will talk about real-life requests, problems, errors. And we will find a solution all of them by one by, by using .Net Core Entity Framework. “Current .Net Core version 3.1.401”.

“We can not solve our problems with the same level of thinking that created them.”

— Albert Einstein

1-) I need to encrypt some properties when saved them to DB. And I need to decrypt them when I read it from DB.

Firstly we must solve this problem globally and manage it from one place.
We will create CryptoData Attribute for signing the secret fields.

CryptoData.cs: This attribute is used for flagged to secret properties of the class.

using System;
using System.Collections.Generic;
using System.Text;
namespace Dashboard.DB.PartialEntites
{
[AttributeUsage(AttributeTargets.All)]
public class CryptoData : System.Attribute
{
public CryptoData()
{
}
public int id { get; set; }
}
}

In this scenario, we will encrypt, User’s Gsm numbers.

DbUser.cs: This is User entity class.

using System;
using System.Collections.Generic;
namespace Dashboard.DB.Entities
{
public partial class DbUser
{
public int IdUser { get; set; }
public string Name { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string PasswordHash { get; set; }
public string Email { get; set; }
public string Gsm { get; set; }
public bool IsDeleted { get; set; }
public bool? IsAdmin { get; set; }
}
}

If you are using Database First, you have to create a MetaData class for signing the property of any DbSet<T> class on DBContext. Because if you put directly [CryptoData] attribute to Gsm property on the existing DbSet class, you could lose your custom changing codes on DataContext. If someone called the scaffolding, all the classes would be generated from the DB again, and all your custom codes are gone.

UserMetaData.cs: This is our UserMetaData class. We signed Gsm property with this class.

public class UserMetaData
{
[CryptoData(id = 5)]
public string Gsm { get; set; }
}

We attached this UserMetaData to the DbUser class, so we signed the Gsm property as a secret field.

[MetadataType(typeof(UserMetaData))]
public partial class DbUser : BaseEntity

This is the example of the Insert method on Repository. We will check all properties of the Inserted entity. And we will look for the marked with “CryptoData” property. If we find it, we will save it as encrypted.

Repository/Insert():

public void Insert(T entity)
{
if (entity == null)
throw new ArgumentNullException(nameof(entity));
using (var dbContext = _provider.GetVbtContext(connection))
{
DbSet<T> _entities = dbContext.Set<T>();
entity = EncryptEntityFields(entity, _context);

entity.UsedTime = DateTime.Now;
_entities.Add(entity);
dbContext.SaveChanges();
}
}

EncryptEntityFields(): Firstly we will check, there is any “MetadataTypeAttribute” in the class. If there is, we will check all property info in Metadata. If we found the” CryptoData” attribute, we will encrypt the selected property’s value.

After all, if you want to encrypt any property of any entities, only adding [CryptoData] attributes on it is enough. You don’t have to write any more code.

Encrypt() & Decrypt() Methods: These are the, encrypt and decrypt methods.

Repository/GetById(): This method is used for getting data from all entities by ID. And if there is any encrypted property in it, we will decrypt and return it.

public virtual T GetById(object id)
{
T entity = Entities.Find(id);
return DecryptEntityFields(entity, _context);
}

“It’s very hard to keep an uncrackable encryption if you share it with the government.”
— John McAfee

DecryptEntityFields: With this method, we will check metadataTypeAttributes, and if we find we will check all properties, and if there is “CryptoData” property info, we will decrypt this field.

2-) I need it when I update any entity; I don’t want to set all fields. I don’t want to lose lots of time to set all properties one by one.

UpdateMatchEntity: All records are assigned from one entity to another without going through column synchronization while updating.

_context.Entry(updateEntity).CurrentValues.SetValues(setEntity);

We will check all property’s current value, and if they are null, we will set the “IsModified” property false. So when the entity creates an Update script, these columns will not be added in the set property. And finally, if there is a CryptoData attribute on any property, it will be encrypted again.

Repository/UpdateMatchEntity:

public virtual void UpdateMatchEntity(T updateEntity, T setEntity, bool isEncrypt = false)
{
if (setEntity == null) //Lates state of the Update Data
throw new ArgumentNullException(nameof(setEntity));
if (updateEntity == null) //Old state of the Updated Data
throw new ArgumentNullException(nameof(updateEntity));
//We will set all properties from setEntity to updateEntity
_context.Entry(updateEntity).CurrentValues.SetValues(setEntity);
//We will check all propery's current value.
foreach (var property in _context.Entry(setEntity).Properties)
{
if (property.CurrentValue == null) {
_context.Entry(updateEntity).Property(property.Metadata.Name).IsModified = false; }
}
updateEntity = UpdateEncryptedEntityFieldIfChange(updateEntity);
_context.SaveChanges();
}

UpdateEncryptedEntityFieldIfChange(): If the class has a Metadata attribute, all the properties of the entity are checked one by one. If one of the properties is signed with CryptoData, it’s value encrypted.
If the current encrypted property is changed, the new value is encrypted and set it over the old one.

This is the example of the usage of the UpdateMatchEntity() method.

“I will always choose a lazy person to do a hard job. Because a lazy person will find an easy way to do it.”

— Bill Gates.

SecurityService/UpdateMatchEntity():

  • “entityViewModel” : This is a security updated model.
  • “_repository.GetById” : We will get the current model of the Security entity.
  • “_repository.UpdateMatchEntity(currentModel, updateModel)”: We will call UpdateMatchEntity() with a current and updated model of the Security entity. So we won’t match updated columns one by one.
public IServiceResponse<SecurityControllerModel> UpdateMatchEntity(SecurityControllerModel entityViewModel, int userId)
{
var response = new ServiceResponse<SecurityControllerModel>(null);
var updateModel = _mapper.Map<DB.Entities.DbSecurityController>(entityViewModel); var currentModel = _repository.GetById(updateModel.IdSecurityController);
if (currentModel != null)
{
_repository.UpdateMatchEntity(currentModel, updateModel);
var returnModel = _mapper.Map<SecurityControllerModel>(currentModel);
response.Entity = returnModel;
}
else
{
throw new NotImplementedException();
}
return response;
}

3-) I don’t want to add “isDelete ==false” condition to all Linq Entity queries.

DB/ISoftDeletable.cs: Firstly we will create an ISoftDeleteable interface for marking the entities, which we select not use “x => !x.Deleted” filter for the query.

using System;
using System.Collections.Generic;
using System.Text;
namespace Dashboard.DB.PartialEntites
{
public interface ISoftDeletable
{
bool Deleted { get; set; }
}
}

DB/PartialEntities.cs: In this example, we marked partial DbUser with the “ISoftDeletable()” interface.

public partial class DbUser : BaseEntity, ISoftDeletable { }

DB/VbtContext.cs: “modelBuilder.AddGlobalFilter()”: When VbtContext class is rising up, OnModelCreating() method is triggered, so we can call our custom AddGlobalFilter() modelBuilder extension.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.AddGlobalFilter();
}

DB/ModelBuilderExtensin.cs: Look for all Entity types in the model and find those that are ISoftDeletable and Call the SetSoftDeleteFilter() method. On this method we will add “x => !x.Deleted” QueryFilter to Entity.

Finally, while writing a Linq query, we are globally filtered deleted records from the entities marked with ISoftDeletable.

4-) I want to add Custom DbSet<T> class to DBContext because of no model type of my SQL query results.

Core/Model/CustomAboneModel.cs: This is the custom result model of AboneService. There is no table like that on SQL. Therefore, we should define this model to DBContext.

namespace Dashboard.DB.Entities
{
public partial class CustomAboneModel : BaseEntity
{
public decimal AboneNo { get; set; }
public string AboneTipiAdi { get; set; }
}
}

Service/Abone/GetAboneByRawSql(): This is our custom subscriber select query service. The return type is “CustomAboneModel.” This class has no equivalent in DBContext. So normally, you can not call FromSqlRaw() method from DBContext on the ”_customAboneRepository” class for this custom model type.

GeneralRepository/GetSql(): This method executes the string raw SQL query with the “FromSqlRaw(sql)” entity method.

public IEnumerable<T> GetSql(string sql)
{
return Entities.FromSqlRaw(sql).AsNoTracking();
}

Description: VbtContext is our inherited of DBContext. And _entities is one of the DBSet of DBContext.

private readonly VbtContext _context;
protected virtual DbSet<T> Entities => _entities ?? (_entities = _context.Set<T>());

Now let’s define the CustomAboneModel to VbtContext.

VbtContext.cs: We must declare “CustomAboneModel” DbSet<>in this class.

  • public DbSet<CustomAboneModel> CustomAboneModel { get; set; }”: We define CustomeAboneModel as DbSet.
  • modelBuilder.Entity<CustomAboneModel>(entity => { entity.HasNoKey(); })”: We have to set HasNoKey for this Entity. If you don’t use it, you get an error. “HasNoKey” Entities are never tracked for changes in the DbContext and therefore are never inserted, updated, or deleted on the database. It is only used for database queries.
public class VbtContext : DashboardContext
{
public VbtContext()
{
}

public DbSet<CustomAboneModel> CustomAboneModel { get; set; }
public VbtContext(DbContextOptions<DashboardContext> options)
: base(options){}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<CustomAboneModel>(entity =>
{
entity.HasNoKey();
});
modelBuilder.AddGlobalFilter();
}
}

5-) I would like to see the SQL command of the LINQ to SQL statements at run time on the console.

This is really a justified request for every developer. Because all developer wants to see, their LINQ query performance, so this query script created independently from the DB and can be seen on the Output Window. Db could be SQL, Oracle, or other database technologies.

Image Source: logging-in-efcore

VbtContext.cs: All DB scripts can be seen only debug mode for the performance. We will use the LoggerFactory class for getting converted linq SQL queries. We will add LogLevel and Database Command Name filter for getting specific log data.

Conclusion:

In this article, we try the solve real-life scenarios With .Net Core 3.1. These items are the features that software developers want from me in real business life. I shared some of them with you. I hope they will help you to save your time.

THE END & GOOD BYE

“If you have read so far, first of all, thank you for your patience and support. I welcome all of you to my blog for more!”

Source: entityframework.net, pluralsight.com, microsoft.com, entityframeworktutorial.net

The Startup

Get smarter at building your thing. Join The Startup’s +724K followers.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store