prevent navigation property insertion - c#

I have an entity A that used as navigation property for another entity B. I would like to control the Insertion for entity A. Thats meen that whenever I would insert A I have to perform other checks and update other entities.
But when I Insert entities of type B it automatically inserts the connected A entities without the extra checks and updates I need.
How can I solve this?
UPDATE
I decided to use this answer as suggested. But in the OnBeforeInsert() I might add a new entities to the context which their OnBeforeInsert() won't be called since in the time var changedEntities = ChangeTracker.Entries(); was called the new entitied wasn't exist yet.
How can I solve this?

EF has very few extension points. So it is sometimes very difficult to customize.
This answer is an extension of my previous answer
public abstract class Entity
{
public virtual void OnBeforeInsert(){}
public virtual void OnBeforeUpdate(){}
}
public class Category : Entity
{
public string Name { get; set; }
public string UrlName{ get; set; }
public override void OnBeforeInsert()
{
//ur logic
}
}
Then in your DbContext class subscribe to ObjectStateManagerChanged event of ObjectStateManager.
public class MyContext : DbContext
{
public override int SaveChanges()
{
//intercept entity changes
UnderlyingObjectContext.ObjectStateManager.ObjectStateManagerChanged
+= OnObjectStateManagerChanged;
var changedEntities = ChangeTracker.Entries();
foreach (var changedEntity in changedEntities)
{
if (changedEntity.Entity is Entity)
{
var entity = (Entity)changedEntity.Entity;
switch (changedEntity.State)
{
case EntityState.Added:
entity.OnBeforeInsert();
break;
case EntityState.Modified:
entity.OnBeforeUpdate();
break;
}
}
}
return base.SaveChanges();
}
ObjectContext UnderlyingObjectContext
{
get
{
return ((IObjectContextAdapter)this).ObjectContext;
}
}
void OnObjectStateManagerChanged(object sender, CollectionChangeEventArgs e)
{
if (e.Action == CollectionChangeAction.Add)
{
//not all added entities are new
if (UnderlyingObjectContext.ObjectStateManager
.GetObjectStateEntry(e.Element).State == EntityState.Added)
{
if (e.Element is Entity)
{
((Entity)e.Element).OnBeforeInsert();
}
}
}
}
}
If you are using EF 4.0 you will need to customize this accordingly.

Related

Calculate NotMapped property when loading from EF Core

We do have an entity class defined as below:
[Table("Users", Schema = "Mstr")]
[Audited]
public class User
{
public virtual string FamilyName { get; set; }
public virtual string SurName { get; set; }
[NotMapped]
public virtual string DisplayName
{
get => SurName + " " + FamilyName;
private set { }
}
}
This is working just fine. Now we would like to extract the logic part SurName + " " + FamilyName to a helper class which is usually injected with dependency injection. Unfortunately DI is not working for an entity class.
Therefor my question: is there any way to intercept the creation of new User objects? Is there a method from EF which I could override to execute some additional logic after a User object was created by EF?
Actually (at least in EF Core 6) you can use DI when constructing entities. Solution is a little bit hacky and based on the EF Core capability to inject "native" services like the context itself into entities constructors:
Currently, only services known by EF Core can be injected. Support for injecting application services is being considered for a future release.
And AccessorExtensions.GetService<TService> extension method which seems to support resolving services from DI.
So basically just introduce ctor accepting your DbContext as a parameter to the entity and call GetService on it and use service:
public class MyEntity
{
public MyEntity()
{
}
public MyEntity(SomeContext context)
{
var valueProvider = context.GetService<IValueProvider>();
NotMapped = valueProvider.GetValue();
}
public int Id { get; set; }
[NotMapped]
public string NotMapped { get; set; }
}
// Example value provider:
public interface IValueProvider
{
string GetValue();
}
class ValueProvider : IValueProvider
{
public string GetValue() => "From DI";
}
Example context:
public class SomeContext : DbContext
{
public SomeContext(DbContextOptions<SomeContext> options) : base(options)
{
}
public DbSet<MyEntity> Entities { get; set; }
}
And example:
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<IValueProvider, ValueProvider>();
serviceCollection.AddDbContext<SomeContext>(builder =>
builder.UseSqlite($"Filename={nameof(SomeContext)}.db"));
var serviceProvider = serviceCollection.BuildServiceProvider();
// init db and add one item
using (var scope = serviceProvider.CreateScope())
{
var someContext = scope.ServiceProvider.GetRequiredService<SomeContext>();
someContext.Database.EnsureDeleted();
someContext.Database.EnsureCreated();
someContext.Add(new MyEntity());
someContext.SaveChanges();
}
// check that value provider is used
using (var scope = serviceProvider.CreateScope())
{
var someContext = scope.ServiceProvider.GetRequiredService<SomeContext>();
var myEntities = someContext.Entities.ToList();
Console.WriteLine(myEntities.First().NotMapped); // prints "From DI"
}
Note that var valueProvider = context.GetService<IValueProvider>(); will throw if service is not registered so possibly next implementation is better:
public MyEntity(SomeContext context)
{
var serviceProvider = context.GetService<IServiceProvider>();
var valueProvider = serviceProvider.GetService<IValueProvider>();
NotMapped = valueProvider?.GetValue() ?? "No Provider";
}
Also you can consider removing not mapped property and creating separate model with it and service which will perform the mapping.
Also in 7th version of EF Core a new hook for exactly this case should be added. See this github issue.
UPD. EF Core 7 approach.
EF 7 adds IMaterializationInterceptor (and bunch of others - see the docs) which can be used exactly for this goal. So updated code can look like the following:
No need for ctor accepting context in entity:
public class MyEntity
{
public int Id { get; set; }
[NotMapped]
public string NotMapped { get; set; }
}
Create an interceptor and overload one of it's methods (I went with InitializedInstance):
class NotMappedValueGeneratingInterceptor : IMaterializationInterceptor
{
public static NotMappedValueGeneratingInterceptor Instance = new ();
public object InitializedInstance(MaterializationInterceptionData materializationData, object entity)
{
if (entity is MyEntity my)
{
var valueProvider = materializationData.Context.GetService<IValueProvider>();
my.NotMapped = valueProvider.GetValue();
}
return entity;
}
}
And add interceptor to the context setup, with our DI approach AddDbContext changes to:
serviceCollection.AddDbContext<SomeContext>(builder =>
builder.UseSqlite($"Filename={nameof(SomeContext)}.db")
.AddInterceptors(NotMappedValueGeneratingInterceptor.Instance));
In your DbContext or whatever your context file is called you can intercept the SaveChanges() method and override it with your own things. In my example I override SaveChanges() to automatically add my audit fields so I don't have to duplicate it all over the code in a million places.
here is my example. So when a new object is being created you can override it. In My example I override both New records Added and Records modified.
These are notated at EntitState.Added and EntityStateModified.
Here is the code.
public override int SaveChanges()
{
var state = this.ChangeTracker.Entries().Select(x => x.State).ToList();
state.ForEach(x => {
if (x == EntityState.Added)
{
//Create new record changes
var created = this.ChangeTracker.Entries().Where(e => e.State == EntityState.Added).Select(e => e.Entity).ToArray();
foreach (var entity in created)
{
if (entity is AuditFields)
{
var auditFields = entity as AuditFields;
auditFields.CreateDateTimeUtc = DateTime.UtcNow;
auditFields.ModifiedDateTimeUtc = DateTime.UtcNow;
auditFields.Active = true;
}
}
}
else if (x == EntityState.Modified)
{
//Modified record changes
var modified = this.ChangeTracker.Entries().Where(e => e.State == EntityState.Modified).Select(e => e.Entity).ToArray();
foreach (var entity in modified)
{
if (entity is AuditFields)
{
var auditFields = entity as AuditFields;
auditFields.ModifiedDateTimeUtc = DateTime.UtcNow;
}
}
}
else
{
//do nothing
}
});
return base.SaveChanges();
}
Since you said:
is there any way to intercept the creation of new User objects?
You would want to do your logic in the EntityState.Added area of code above and this will allow you to intercept the creation of your new User and do whatever you want to do before it is saved to Database.

"An entity object cannot be referenced by multiple instances of IEntityChangeTracker" while I am inserting on a many-to-many situation?

After reading similar issues I still did not managed to overcome this problem. What is interesting is that I decoupled and made a separate project and there it works.
I have two entities JobAnnouncement and Skill and they are in a many-to-many relationship. I am able to insert as long as I do not have any Skills added to the job announcement.
Let me put in the code:
public class JobAnnouncement
{
public int Id { get; set; }
public DateTime DateAdded { get; set; }
//other properties
public virtual ICollection<Skill> Skills { get; set; }
}
public class Skill
{
//other properties
public virtual ICollection<JobAnnouncement> JobAnnouncements { get; set; }
}
The insertion code is (tried all ways including the dumb ones):
public void AddJobAnnouncement(JobAnnouncement jobAnnouncemnet)
{
_jobAnnouncement.Content = jobAnnouncemnet.Content; //ok
_jobAnnouncement.CompanyId = jobAnnouncemnet.CompanyId; //ok
_jobAnnouncement.Skills = jobAnnouncemnet.Skills; //fails
db.JobAnnouncements.Add(_jobAnnouncement); //throws exception
db.SaveChanges();
}
Just for your information I did override the SaveChanges() method (that is why I left the DateAdded. I don't think that's causing the error, I tryied removin it and it still doesn't work.)
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries().Where(entry => entry.Entity.GetType().GetProperty("DateAdded") != null))
{
if (entry.State == EntityState.Added)
{
entry.Property("DateAdded").CurrentValue = DateTime.Now;
}
if (entry.State == EntityState.Modified)
{
entry.Property("DateAdded").IsModified = false;
}
}
}
}
Also, I am using Niject, and the instruction found on SO does not help:
kernel.Bind<CareerTrackContext>().To<CareerTrackContext>().InRequestScope();
I've even tried to alter the entity lifespan. The version of Entity Framework is 6.1.3 and I am using code first.
I just don't see the issue. What can I try next?
I found a fix ... not necessarily a solution. The AddJobAnnouncement should look like this:
public void AddJobAnnouncement(JobAnnouncement jobAnnouncemnet)
{
_jobAnnouncement.Content = jobAnnouncemnet.Content; //ok
_jobAnnouncement.CompanyId = jobAnnouncemnet.CompanyId; //ok
// it will by ok now
jobAnnouncement.Skills = new List<Skill>();
foreach (var skill in jobAnnouncemnet.Skills)
{
_jobAnnouncement.Skills.Add(db.Skills.Find(skill.Id));
}
}
Right now I don't even now why it works.

Save value in uppercase when adding row to database

I have three columns in the db table that looks as follow:
When I add a new row, it should store the value on column fieldname in uppercase. How can I do that?
Since you tagged the question with entity framework, I assume you want to do it in your data layer or close to DB. There's a number of ways for doing this.
You could override SaveChanges() in your context. This will move the logic away from the model, but still ensure that the correct value is saved. Also, if you want it on several entities you can use an interface. When it's an interface you can do it for several of your entities without any duplicate code, as long as it's the same property. Otherwise you would need an attribute and reflection. Reusability is pretty high, but it adds some overhead to your SaveChanges().
public class CustomerEntity()
{
public string Name {get;set;}
}
public MyCustomContext : DbContext
{
// Other stuff...
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries<CustomerEntity>())
{
if (entry.State == EntityState.Modified || entry.State == EntityState.Added)
{
// Possibly check for null or if it's changed at all.
entry.Entity.Name = entry.Entity.Name.ToUpper();
}
}
return base.SaveChanges();
}
}
And with an interface:
public interface INameIsAlwaysUpperCase
{
string Name {get;set;}
}
public MyCustomContext : DbContext
{
// Other stuff...
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries<INameIsAlwaysUpperCase>())
{
if (entry.State == EntityState.Modified || entry.State == EntityState.Added)
{
// Possibly check for null or if it's changed at all.
entry.Entity.Name = entry.Entity.Name.ToUpper();
}
}
return base.SaveChanges();
}
}
You can add a custom validation. This will throw exception if it's not saved correctly. That way you can move the responsibility to the consumer of the model. However, depending on your scenario, you might not want to throw an exception. This is my favourite since it forces the consumer to do it the right way. As per comments, why throw when you can silently convert it? Yes, it's a valid question. For me it's about forcing the consumer of the data layer to use it correctly, and not let the daya layer decide what to to with the data. I personally don't like it when the business layer asks the data layer to save one thing, and then the data layer saves another thing. If lower case isn't a valid option, then it shouldn't be saved. I don't think it's much more different from using [Required]. But it's really about context and what works in your particular case.
public class CustomerEntity() : IValidatableObject
{
public string Name {get;set;}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// Possibly check for null here as well...
if (this.Name.ToUpper() != this.Name)
{
yield return new ValidationResult("You need to save as upper!");
}
}
}
Use a property that manages this for you. This may be the simplest solution, even if I like to keep my entities "clean". It's absolutely the solution that will require least effort. However, the reusability is low, and what if you use your entitites all over the application and want the value to be lower case until it's actually saved? That's not possible. But, again, I think it comes down to your particular situation. If you want the value to be upper case even before you save it, this is probably the best solution!
public class CustomerEntity()
{
string _name;
public string Name
{
get { return _name; }
set { _name = value.ToUpper(); } // Check for null ?
}
}
Do it when saving. This moves the logic to when you're saving your entity. This is probably the least preferable option, since the reusability is non-existing. What happens in Update()? However, the OP specifically states "When I add a new row", so it may only be applicable when adding new entities. And in that case it could very well be the most prefered choice since it allows updates to have lower case. But it would have to depend on the use case.
public void AddCustomer(string name)
{
var customer = new CustomerEntity
{
Name = name.ToUpper()
};
_context.Customers.Add(customer);
}
Just use properties. If your model is as below:
public class MyModel
{
public int Id { get; set; }
public string Description { get; set; }
public string LanguageCode { get; set; }
public string FiledName { get; set; }
}
Then, change it to:
public class MyModel
{
private string fieldName;
public int Id { get; set; }
public string Description { get; set; }
public string LanguageCode { get; set; }
public string FiledName
{
get { return filedName; }
set
{
if(!string.IsNullOrEmpty(value))
fieldName = value.ToUpper();
else
fieldName = value;
}
}
}
Try this.
public string FiledName
{
get { return filedName; }
set
{
filedName = !string.IsNullOrEmpty( value ) ? value.ToUpper() : value;
}
}
Using a ValueConverter on the Property could be an effective way to do this.
public class YourDbContext : DbContext
{
public YourDbContext(DbContextOptions<YourDbContext> options)
: base(options)
{
}
public DbSet<Row> Rows { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
var converter = new ValueConverter<string, string>(
v => v.ToUpper(), // writing
v => v
);
// just one property
modelBuilder.Entity<Row>()
.Property(x => x.Column)
.HasConversion(converter);
// all of the string properties
foreach (var entityType in builder.Model.GetEntityTypes())
{
foreach (var property in entityType.GetProperties())
{
if (property.ClrType == typeof(string))
{
builder.Entity(entityType.Name)
.Property(property.Name)
.HasConversion(converter);
}
}
}
}
}
It's also possible to use a Custom Attribute :
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class Standardized : Attribute
{}
Then decorate properties inside your model :
public class MyModel
{
public string Id{ get; set; }
[Required]
[Standardized]
public string Description { get; set; }
}
Taken from #smoksnes accepted answer, inside your DbContext class, override SaveChanges(), SaveChangesAsync() (EF Core 5.x) and add a private method using reflection to obtain decorated properties and apply transformations, like this :
public override int SaveChanges()
{
StandardizeBeforeSaving();
return base.SaveChanges();
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
StandardizeBeforeSaving();
return await base.SaveChangesAsync(cancellationToken);
}
private void StandardizeBeforeSaving()
{
foreach (var entry in ChangeTracker.Entries())
{
if (entry.State == EntityState.Modified || entry.State == EntityState.Added)
{
var properties = entry.Entity
.GetType()
.GetProperties()
.Where(prop => Attribute.IsDefined(prop, typeof(Standardized)) && prop.PropertyType == typeof(string));
foreach (var property in properties)
{
var value = entry.CurrentValues[property.Name]?.ToString() ?? string.Empty;
entry.CurrentValues[property.Name] = value.Standardize();
}
}
}
}
Just be aware that reflection could be slower than other techniques presented in accepted answer. But for most scenarios (ie. user updates or creates couple of entities with not that many properties) it should be fine.

insert in other tables after calling savechanges()

I made a simple framework to Doing the CRUD using MVVM for my application as below (for the add method ):
public int Add<TEntity>(TEntity entity) where TEntity : class
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
string entityName = GetEntityName<TEntity>();
var fqen = GetEntityName<TEntity>();
object originalItem;
EntityKey key = ObjectContext.CreateEntityKey(fqen, entity);
if (ObjectContext.TryGetObjectByKey(key, out originalItem)) return 0;
ObjectContext.AddObject(entityName, entity);
int r = ObjectContext.SaveChanges();
return r;
}
//And the Context
private ObjectContext ObjectContext
{
get
{
return GlobalContext.MainObjectContext;
}
}
//and the Singltone
public static S_Entities MainObjectContext
{
get
{
return Singleton.Instance();
}
}
in the ViewModel the Save() is implemented as below:
public void SaveItem()
{
storeReceiptBusiness.Insert(CurrentItem);
Items.Add(CurrentItem);}
the problem is when is call the Save() ,The CurrentItem will insert as expected but the currentItem of the last added entity and the CurrentItem of the current ViewModel will added!
I need to Discard old changes and only add the Current value.
hope it was clear.
Thanks indeed.

Entity Framework 6.1 Discriminator TPH

I am looking to implement Table-per-Hierarchy using EF6 similar to the instructions found here:
example.
I have an abstract base class of User with the following derived types:
Student
Contact
Instructor
When I examine the database table Users the discriminator column value is (Undefined) when I pass a student object into my Save method below. Instead I would expect the value to be Student. Otherwise my data is saved correctly in both the Users and Students tables.
While troubleshooting the problem I added a UserType enumerator Get property to the classes to ensure that I am casting from User to Student.
In my UserRepository class my Save method is below.
public void Save(User user)
{
if (Exists(user.Id))
UpdateUser(user);
else
{
switch (user.Role)
{
case UserType.Role.Base:
_db.Users.Add(user);
break;
case UserType.Role.Student:
_db.Users.Add(user as Student);
break;
case UserType.Role.Instructor:
_db.Users.Add(user as Instructor);
break;
case UserType.Role.Contact:
_db.Users.Add(user as Contact);
break;
}
}
_db.SaveChanges();
}
Failed Alternative
I've tried code like the following to explicitly create a new Student.
private void MapToStudent(User user)
{
_db.Users.Add(new Student()
{
FirstName = user.FirstName,
LastName = user.LastName,
//...
});
}
Question
I am not downcasting correctly? Or rather what is the proper/preferred way to save subclasses using EF?
User Base Class
public abstract class User
{
public int Id { get; set; }
//...
}
internal class UserNotFound: User
{
public override UserType.Role Role
{
get
{
return UserType.Role.Base;
}
}
}
public class Student : User
{
//...
public override UserType.Role Role
{
get { return UserType.Role.Student; }
}
}
public class Contact : User
{
//...
public override UserType.Role Role
{
get { return UserType.Role.Contact; }
}
}
public class Instructor : User
{
//...
public override UserType.Role Role
{
get { return UserType.Role.Instructor; }
}
}
DatabaseContext Mapping
public class DatabaseContext : Context
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>().ToTable("Students");
modelBuilder.Entity<Contact>().ToTable("Contacts");
modelBuilder.Entity<Instructor>().ToTable("Instructors");
}
}
It appears your mappings are incorrect for TPH. The linked example in your questions shows:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<BillingDetail>()
.Map<BankAccount>(m => m.Requires("BillingDetailType").HasValue("BA"))
.Map<CreditCard>(m => m.Requires("BillingDetailType").HasValue("CC"));
}
which modeled after your question might look like:
modelBuilder.Entity<User>()
.Map<Student>(m => m.Requires("Discriminator").HasValue("STU"))
.Map<Instructor>(m => m.Requires("Discriminator").HasValue("INS"));

Categories

Resources