I'm having a rather interesting problem and I am not quite sure how to properly get past it. In order to fully understand my problem, please keep in mind the following:
I am "Modulizing" my features. For example I have written a "Logger" dll that is then turned into a package. This DLL has its own DbContext and knows about certain tables as a result. Then I have written a "Tracker" dll which extends the Logger dll. The tracker dll is another module with its own Db Context and its own tables. It knows about the Logger dll only in the fact that it knows about its service layer and its model layer. Let me show you what that looks like:
Here are the Models (representing tables)
//Logger Module
public class LogError : ILogError
{
public Guid Id { get; set; }
//more stuff not relavent to the problem
}
//Tracker Module
public class ErrorTicket : IErrorTicket
{
public Guid Id { get; set; }
public Guid LogErrorId { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int TicketNumber { get; set; }
//More properties not related to the problem
public virtual LogError LogError { get; set; }
public virtual ILogError MyLogError => LogError;
public virtual ICollection<ErrorTicketNote> ErrorTicketNotes { get; set; }
public virtual IEnumerable<IErrorTicketNote> MyErrorTicketNotes => ErrorTicketNotes;
}
Please keep in mind for the ErroTicket Class, I am using interfaces to expose certain methods. For example my interface only has getters and no setters, so when an interface is passed, the class cannot be updated. I would rather not go into a discussion as to why I do that as I am very certain it is not part of the problem. Just wanted to make a not so you understand why I have LogError and then MyLogError listed up there.
Now for my DbContext I have the following:
//Logger Module
public class LoggerDbContext : DbContext, ILoggerDbContext
{
public DbSet<Model.LogError> LogError { get; set; }
public DbSet<Model.LogInfo> LogInfo { get; set; }
public IEnumerable<ILogError> LogErrors => LogError;
public IEnumerable<ILogInfo> LogInfos => LogInfo;
public LoggerDbContext(string connectionString = "DefaultConnection") : base(connectionString) { }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
base.OnModelCreating(modelBuilder);
}
public void CreateLog(ILogError logError)
{
LogError.Add((Model.LogError) logError);
SaveChanges();
}
public void CreateLog(ILogInfo logInfo)
{
LogInfo.Add((Model.LogInfo) logInfo);
SaveChanges();
}
}
//Tracker Module
public class TrackerDbContext : DbContext, ITrackerDbContext
{
public DbSet<ErrorTicket> ErrorTicket { get; set; }
public DbSet<ErrorTicketNote> ErrorTicketNote { get; set; }
public IEnumerable<IErrorTicket> ErrorTickets => ErrorTicket;
public IEnumerable<IErrorTicketNote> ErrorTicketNotes => ErrorTicketNote;
public TrackerDbContext(string connectionString = "DefaultConnection") : base(connectionString) { }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
base.OnModelCreating(modelBuilder);
}
public void CreateTicket(IErrorTicket errorTicket)
{
ErrorTicket.Add((ErrorTicket) errorTicket);
SaveChanges();
}
public void ModifyTicket(IErrorTicket errorTicket)
{
Entry(errorTicket).State = EntityState.Modified;
SaveChanges();
}
public void CreateTicketNote(IErrorTicketNote errorTicketNote)
{
ErrorTicketNote.Add((ErrorTicketNote) errorTicketNote);
SaveChanges();
}
public void ModifyTicketNote(IErrorTicketNote errorTicketNote)
{
Entry(errorTicketNote).State = EntityState.Modified;
SaveChanges();
}
}
As you can see two DbContext classes do not know about each other, but through my models I create a relation of foreign key. Now to my problem.
When an error occurs I have code that runs the following:
//the line bellow calls a Logger service that ends up invoking the method from the Logger DbContext - public void CreateLog(ILogError logError)
var var logError = _databaseLoggerService.Error(exception, message);
//Then I try to create my ErrorTicket and I assign the logError object to the class to create the relation of the foreign key.
var currentTime = DateTime.Now;
var errorTicket = new ErrorTicket
{
Id = Guid.NewGuid(),
LogErrorId = logError.Id,
TimeCreated = currentTime,
TimeResolved = null,
TimeOfFirstOccurrence = currentTime,
TimeOfLastOccurrence = currentTime,
TotalOccurrences = 1,
Resolved = false,
Resolution = string.Empty,
CommitNumber = string.Empty,
TimeImplemented = null,
LogError = (LogError) logError
};
_trackerDbContext.CreateTicket(errorTicket);
The problem that I get is the following:
Violation of PRIMARY KEY constraint 'PK_dbo.LogError'. Cannot insert duplicate key in object 'dbo.LogError'. The duplicate key value is (769fb127-a8d8-40de-9492-fc61ca86cb16).
The statement has been terminated.
If I look at my LogError table, a record indeed exists with that key. I assume it was created when I called _databaseLoggerService.Error(exception, message);
What I do not understand, is why is this a problem for EF6 or how to get past it?
I have done a lot of research on the topic and I have found articles which state that because it is 2 sepparate DbContextes the second one may not know that the record exist so it when I caled .Add method, it marked ALL objects for insertoin and thus generated the INSERT queries. Which makes sense, and I could simply not call my db creation and let my tracker just create both objects for me. Which is all fine, however, the problem I have with that is when I try to modify 'the record, I get the exact same problem. Even though I marked the record as modify, it generates the insert queries.
My question is how do I get past this problem?
Related
This is a tale of optional owned entities and foreign keys.
I'm working with EF 5 (code first) and I do this :
public class Parent {
public Guid Id { get; private set; }
public OwnedType1? Owned1 { get; private set; }
public OwnedType2? Owned2 { get; private set; }
public Parent(Guid id, OwnedType1? owned1, OwnedType2? owned2) {
Id = id; Owned1 = owned1; Owned2 = owned2;
}
}
public class OwnedType1 {
public Guid? OptionalExternalId { get; private set; }
public OwnedType1 (Guid? optionalExternalId) {
OptionalExternalId = optionalExternalId;
}
}
public class OwnedType2 {
public Guid? OptionalExternalId { get; private set; }
public OwnedType2 (Guid? optionalExternalId) {
OptionalExternalId = optionalExternalId;
}
}
public class Shared {
public Guid Id { get; private set; }
public Shared (Guid id) {
Id = id;
}
}
Now, the configuration :
//-------- for Parent ------------
public void Configure(EntityTypeBuilder<Parent> builder) {
builder
.ToTable("Parents")
.HasKey(p => p.Id);
builder
.OwnsOne(p => p.Owned1)
.HasOne<Shared>()
.WithMany()
.HasForeignKey(x => x.OptionalExternalId);
builder
.OwnsOne(p => p.Owned2)
.HasOne<Shared>()
.WithMany()
.HasForeignKey(x => x.OptionalExternalId);
}
//-------- for OwnedType1 ------------
// (there's no builder as they're owned and EntityTypeBuilder<Parent> is enough)
//-------- for OwnedType2 ------------
// (there's no builder as they're owned and EntityTypeBuilder<Parent> is enough)
//-------- for Shared ---------------
public void Configure(EntityTypeBuilder<Shared> builder) {
builder
.ToTable("Shareds")
.HasKey(p => p.Id);
}
Side note : If you're wondering why OwnedType1 and OwnedType2 don't each have a property called 'ParentId', it's because it's created implicitly by the "OwnsOne".
My problem is this :
When I create a new Migration, then OwnedType1 works like a charm, but for OwnedType2 (which is quasi-identical), I get his error :
The property 'OptionalExternalId' cannot be added to the type
'MyNameSpace.OwnedType2' because no property type was specified and
there is no corresponding CLR property or field. To add a shadow state
property, the property type must be specified.
I don't understand what it's complaining about. And why it's complaining only for one of them.
I know that you probably can't work it out with this simplified version of my schema, but what I'm asking is what you think it might be (follow your guts of EF guru) :
Some missing constructor?
Incorrect visibility on one of the fields?
Bad navigation definition?
A typo?
Something tricky (like : If you're going to have TWO different entity classes having a one-to-many relation with Shared, then they can't use the same name for external key. Or I need to use a composite key. Or whatnot).
It was a configuration issue that had nothing to do with Owned entities. Another case of "EF error message is obscure but issue is somewhere there in plain sight".
Unfortunately I don't remember how I fixed it. But it was along the lines of "Need an extra constructor with all the paramaters" or "one of the fields had a different name in the constructor parameters" or one of those classic EF mishaps.
I have a one-to-many relationship that am trying to update but get the error
Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
UPDATED
The update method will work for as long as the beneficiaries collection is not being changed or updated.
The code looks like
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
private readonly List<Beneficiary> _beneficiaries;
public IEnumerable<Beneficiary> Beneficiaries => _beneficiaries;
public void AddBeneficiary(string name)
{
var beneficiary = new Beneficiary(Id, name);
_beneficiaries.Add(beneficiary);
}
}
public sealed class Beneficiary
{
public int Id { get; set; }
public int EmployeeId { get; private set; }
public string Name { get; private set; }
public Beneficiary(int employeeId, string name)
{
Name = name;
EmployeeId = employeeId;
}
}
//Extracted from repo method
public void Update(TEntity entity)
{
if (entity != null)
_dbSet.Update(entity);
}
// Extracted Actual update from application service
if (incomingEmployee.Beneficiaries.Any())
{
if (employeeFromStore.Beneficiaries.Any())
{
employeeFromStore.Beneficiaries.ToList()
.ForEach(existingBeneficiary =>
employeeFromStore.RemoveBeneficiary(existingBeneficiary)); //Method skipped for brevity
//Tried calling this too
//_employeeRepository.UnitOfWork.SaveChanges();
}
incomingEmployee.Beneficiaries.ToList().ForEach(beneficiary =>
{
employeeFromStore.AddBeneficiary(beneficiary.Name);
});
}
_employeeRepository.Update(employeeFromStore);
_employeeRepository.UnitOfWork.SaveChanges();
I feel the issue is in how the tracker treats the update for navigation properties collection but I could be wrong.
I have also attempted to use the change tracker for updates with a method like below
public void ApplyCurrentValues<TEntity>(TEntity original, TEntity current)
where TEntity : class
{
//if it is not attached, attach original and set current values
base.Entry<TEntity>(original).CurrentValues.SetValues(current);
}
Does anyone have an idea how to go about this?
I finally made headway after reading about how update works. Basically as mentioned here
, and this issue. When Update method is invoked the state of the whole entity including all reachable entities through navigation properties is marked as modified.
If you are trying to add beneficiaries to an existing employee, the SaveChanges() throws since it expects to modify a record(s) with the primary key(s) provided but finds none in the database. Update method seemed not the best candidate.
I opted to make the Update method from the repository base overridable in the EmployeeRepository as below
//Update method in the repository base
//Made method virtual to enable overriding
public virtual void Update(TEntity entity)
{
if (entity != null)
_dbSet.Update(entity);
}
//Update method from EmployeeRepository
public override void Update(Employee employee)
{
var employeeFromStore = _unitOfWork.Employees.Where(p => p.Id == employee.Id)
.Include(p => p.Beneficiaries)
.SingleOrDefault();
if (employeeFromStore != null)
{
context.Entry(employeeFromStore).CurrentValues.SetValues(employee);
//Replacing the whole collection
foreach (var beneficiary in employeeFromStore.Beneficiaries)
{
if (employee.Beneficiaries.Any())
context.Entry(beneficiary).State = EntityState.Deleted;
}
//Adding new collection
foreach (var beneficiaryToAdd in employee.Beneficiaries)
{
var newBeneficiary = new Beneficiary(beneficiaryToAdd.EmployeeId, beneficiaryToAdd.Name);
employeeFromStore.AddBeneficiary(newBeneficiary.Name);
}
}
}
I believe this answer can be improved or tweaked to suit specific situations. For instance, other than clearing the persisted collection, one can opt for an update
I have a question, why my Strategy property is null, when i getting all DbSet from context? How to [NotMapped] property make visible in my backend?
My class looks like this:
public class Machine
{
[Key]
public int Id { get; set; }
[NotMapped]
public WorkStrategy Strategy { get; set; }
public double GetManHours() => Strategy.TimeOfWork(HoursPerDay);
}
WorkStrategy is an abstract class:
public abstract class WorkStrategy
{
public abstract double TimeOfWork(double hours);
}
public class FarmStrategy : WorkStrategy
{
public override double TimeOfWork(double hours) => // do things
}
public class CultivationStrategy : WorkStrategy
{
public override double TimeOfWork(double hours) => //do things
}
Part of my Seed method where i seeding machines looks like this:
//Machines
for(int i = 0; i < countOfMachines; i++)
{
Machine machine = new Machine { Id = i + 1 };
machine.Strategy = new FarmStrategy;
modelBuilder.Entity<Machine>().HasData(machine);
}
But when i call Machines from DB:
var machines = _context.Machines;
The Strategy property is null. Could you tell me, how to attach [NotMapped] property while seeding a db
? Is it possbile?
EDIT
When i want to add WorkStrategy as not "notmapped" i get an error from EF while i adding migration:
The entity type 'WorkStrategy' requires a primary key to be defined
But i dont want to make an table for WorkStrategy.
EDIT
My OnModelCreating in context class:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Machine>().Ignore(x => x.Strategy);
builder.Seed();
base.OnModelCreating(builder);
}
Its not work as [NotMapped]
You can use fluent api ignore instead of notmapped
modelBuilder.Entity<Machine>().Ignore(x => x.Strategy );
I think your Problem is not the not mapped Attribute, but the Structure of your Classes.
If you had a Flag, which Type of Strategy is needed and adapt the Strategy-Property depending on that Flag to initialize a Strategy if it’s null, you could keep your Notmapped-Attribute or the Ignore-Method with Fluent-Api.
I'm struggling to understand why when I remove a child Settings object from MyUser.Settings and SAVE MyUser I get SQL errors like below:
Cannot insert the value NULL into column 'MyUserId', table '###.Settings'; column does not allow nulls. UPDATE fails.
The statement has been terminated.
What I would expect to happen is that removing the item from the collection, then saving MyUser causes NHibernate to issue a DELETE command for the given child. However, what it does is UPDATE the relevant row for the Settings object, setting MyUserId to NULL - which isn't allowed as I'm using a Composite Key.
I've tried so many combinations of Inverse() and the various Cascade options but nothing seems to work. I should point out that Adding to the collection works perfectly when I save MyUser.
I'm totally baffled!
Below is pseudo code to try and explain my entities and mappings.
public class SettingType
{
public virtual int SettingTypeId { get; set; }
public virtual string Name { get; set; }
public virtual bool Active { get; set; }
}
public class Setting
{
public virtual MyUser MyUser { get; set; }
public virtual SettingType SettingType { get; set; }
public virtual DateTime Created { get; set; }
}
public class MyUser
{
public virtual int MyUserId { get; set; }
public virtual IList<Setting> Settings { get; set; }
public virtual string Email { get; set; }
public void AddSetting(SettingType settingType, DateTime now)
{
var existing = _settings.SingleOrDefault(s => s.SettingType.SettingTypeId == settingType.SettingTypeId);
if (existing != null)
{
existing.Updated = now;
}
else
{
var setting = new Setting
{
MyUser = this,
SettingType = settingType,
Created = now,
};
_settings.Add(setting);
}
}
public void RemoveSetting(SettingType settingType)
{
var existingPref = _settings.SingleOrDefault(s => s.SettingType.SettingTypeId == settingType.SettingTypeId);
if (existingPref != null)
{
_settings.Remove(existingPref);
}
}
private readonly IList<Setting> _settings = new List<Setting>();
}
And my mappings:
public class SettingTypeMap : IAutoMappingOverride<SettingType>
{
public void Override(AutoMapping<SettingType> mapping)
{
mapping.Table("SettingTypes");
mapping.Id(m => m.SettingTypeId).GeneratedBy.Identity();
mapping.Map(m => m.Name).Not.Nullable().Length(100);
mapping.Map(m => m.Active).Not.Nullable().Default("0");
}
}
public class SettingMap : IAutoMappingOverride<Setting>
{
public void Override(AutoMapping<Setting> mapping)
{
mapping.Table("Settings");
mapping.CompositeId()
.KeyReference(m => m.MyUser)
.KeyReference(m => m.SettingType);
mapping.Map(m => m.Created).Not.Nullable().Default("CURRENT_TIMESTAMP");
mapping.Map(m => m.Updated).Nullable();
}
}
public class MyUserMappingOverride : IAutoMappingOverride<MyUser>
{
public void Override(AutoMapping<MyUser> mapping)
{
mapping.Table("MyUsers");
mapping.Id(m => m.MyUserId).GeneratedBy.Identity();
mapping.Map(m => m.Email).Not.Nullable().Length(200);
mapping.HasMany(m => m.Settings).KeyColumn("MyUserId").Cascade.DeleteOrphan()
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);
}
}
All using:
FluentNHibernate v1.3.0.733
NHibernate v3.3.1.4000
UPDATE: After a few suggestions I've tried to change the mapping for MyUser entity.
First to this:
mapping.HasMany(m => m.Settings)
.KeyColumn("MyUserId")
.Inverse()
.Cascade.DeleteOrphan()
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);
This gives the error: Given key was not present in the dictionary
So tried to add second key column:
mapping.HasMany(m => m.Settings)
.KeyColumn("MyUserId")
.KeyColumn("SettingTypeId")
.Inverse()
.Cascade.DeleteOrphan()
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);
But this then causes odd behaviour when loading the Settings collection from the DB for a given MyUserId. Looking at the nh profiler I see a second SELECT ... FROM Settings but setting the SettingTypeId same as value for MyUserId.
Still totally baffled. Has cost me too much time so going to revert to adding a primary key id field to the Settings entity. Maybe you just can't do what I'm trying using NHibernate. In pure SQL this is simple.
You should use the Inverse mapping
mapping.HasMany(m => m.Settings)
.KeyColumn("MyUserId")
.Inverse()
.Cascade.DeleteOrphan()
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);
This will allow NHibernate to ask the setting itself to be deleted. Otherwise, NHibernate firstly tries to delete the relation, and would try to delete the entity.
See: 6.4. One-To-Many Associations
Very Important Note: If the column of a
association is declared NOT NULL, NHibernate may cause constraint
violations when it creates or updates the association. To prevent this
problem, you must use a bidirectional association with the many valued
end (the set or bag) marked as inverse="true". See the discussion of
bidirectional associations later in this chapter.
Just getting my feet wet with some Fluent NHibernate AutoMap conventions, and ran into something I couldn't figure out. I assume I'm just not looking in the right place...
Basically trying to enforce NOT-NULL on the "many" side of the one to many relationship.
It seems, using the automapping, it always makes the parent property Id nullable in the database.
I did some searching on StackOverFlow and found similar questions, but nothing relating to AutoMapping and Conventions though (unless I missed it).
Quick example...
public class Group // One Group
{
public Group() { this.Jobs = new List<Job>(); }
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Job> Jobs { get; protected set; }
}
public class Job // Has many Jobs
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
// Trying to make this field not-nullable in the database.
public virtual Group Group { get; set; }
}
I thought I'd be able to just create a convention like...
public class OneToManyConvention : IHasOneConvention
{
public void Apply(IOneToOneInstance instance)
{
// Nullable() isn't a valid method...
instance.Not.Nullable();
}
}
But it seems IOneToOnInstance doesn't have a Nullable() method. I can do this if I create a Map file for Job, but trying to avoid any Map files and stick with auto-mapping.
I came across this link on the Fluent group list describing something similar.
Which describes something like this...
public class NotNullPropertyConvention : IPropertyConvention
{
public bool Accept(IProperty target)
{
return true;
}
public void Apply(IProperty target)
{
target.Not.Nullable();
}
}
But that raises the questions of...
1) How would I determine IProperty to be a Job (or any child property that is a link back to the parent)
2) It made a mention on that page that using this would override my manual overrides, eg. if a very specific property link needed to be NULL. Which would be an issue (if it's still an issue, but can't test without figuring out #1 first)
Any ideas on this? Am I just missing something?
Update 1
Still no go. Even the following still doesn't enforce Not-Nullable in the database schema...
public class FluentConvention : IPropertyConvention
{
public void Apply(IPropertyInstance instance)
{
instance.Not.Nullable();
}
}
It does for all of the other fields though...
/shrug
Any ideas?
Update 2
While this isn't the answer I was looking for, I did find a work around...
I was using NHibernate Validator assembly, and within that assembly there is a [NotNull] attribute. If I decorated my class with the Validator attribute, and associated the ValidationEngine to NHibernate before the schema creation, it would tag the FK database column as Not-Nullable.
public class Job // Has many Jobs
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
[NHibernate.Validator.Constraints.NotNull]
public virtual Group Group { get; set; }
}
If anyone needs the full code for the NHibernate + ValidationEngine initialization, just let me know.
Still looking for a way to do it using the pure mapping convention route though if anyone has any info...
Thanks!
You can override the auto-mapped properties as part of your AutoMap in Fluenttly.Configure().
So you can do this:
.Override<Job>(map => map.References(x => x.Group).Not.Nullable())
It's not exactly convenient if you have a lot of classes that need this though.
Edit:
You can also specify the override in a class that implements IAutoMappingOverride like so:
public class JobMappingOverride : IAutoMappingOverride<Job>
{
public void Override(AutoMapping<Job> mapping)
{
mapping.References(x => x.Group).Not.Nullable();
}
}
and include it like so:
.UseOverridesFromAssemblyOf<JobMappingOverride>()
This would keep your fluent configuration a little cleaner.
It seems that IPropertyConvention is only called on simple properties of your classes. If your property references another class, you need to use IReferenceConvention too.
Try this:
public class FluentConvention : IPropertyConvention, IReferenceConvention
{
public void Apply(IPropertyInstance instance)
{
instance.Not.Nullable();
}
public void Apply(IManyToOneInstance instance)
{
instance.Not.Nullable();
}
}