I have been reading some artices about using the RelationshipManager to gain access to the entries that have related data. It is still unclear to me what the best way to audit when an entity whose related data is added or updated.
Sample Classes:
public class Rfi
{
public Guid Id {get;set;}
public string Number {get;set;}
public virtual ICollection<Attachment> Attachments {get;set;}
}
public Class Attachment
{
public Guid Id {get;set;}
public string Name {get;set;}
public string Description {get;set;}
public string FileName {get;set;}
public string Path {get;set;}
}
Sample Mappings:
public class RfiMapping: EntityTypeConfiguration<Rfi>
{
public Rfimapping()
{
HasMany(r => r.Attachments).WithMany().Map(m =>
{
m.MapLeftKey("RfiId");
m.MapRightKey("AttachmentId");
m.ToTable("Rfi_Attachments");
});
}
}
I am using the Repository and Unit Of Work patterns. My UoW inherits from DbContext. A repository call may look like this:
public void AddAttachmentToRfi(Attachment attachment, Guid rfiId)
{
var rfi = _rfiRepository.FindById(rfiId);
rfi.Attachments.Add(attachment);
_rfiRepository.UnitOfWork.Commit();
}
Is it possible , in an overridden SaveChanges method, to figure out that an Attachment entity was added to an Rfi entity? When I traverse the, say ChangeTracker.Entries, I am not seeing its state being set to modified. Which makes sense, because I am only adding to the relationships and not the entity directly.
I know to cast my DbContext to an IObjectContextAdapter, but I am not sure what I need to do with the RelationshipManager to get the changes made to any of the relationships. I am also curious to know if I were to update an Attachment's Description property later on, if I can still see what changes were made to any related data.
My goal with this is, the user interface for the Rfi allows users to attach files (Rfi is obviously not the only entity that can have attachments). I need to show a history of everything that happens to an Rfi. This means if an attachment is added I need to audit it. If the attachment's data is updated, I need to audit those changes and show that they were updated via the Rfi interface. This may get complicated if that attachment is shared with another entity, but I will cross that road later.
As you say you are not changing any of the entities only the relationship between them.
EF will then convert this to an insert into or delete from the Rfi_Attachments table.
One way to audit this is to add a database trigger that writes an entry to a log table, each time an entry is added or deleted.
Related
I have a model like
public class MyEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Required]
public int Id { get; set; } // Id
[Required]
[Key]
public System.Guid GUID { get; set; }
}
The GUID property is the PK by design, but I have a db generated Id property that I use within my code to determine if the object is a new object that hasn't been saved yet.
When I save this object with Entity Framework, the Id property does not get back populated as normally happens for database generated properties (although usually these are keys). I have to query the DB for the object and grab the ID manually. It seems EF only back populates Key properties on SaveChanges.
Is there any way to get EF to automatically populate the Id property here? Setting it as the Key is not an option, I have dozens of tables that are FK'd to the GUID property and for good reason.
EDIT: I have discovered that the package https://entityframework-extensions.net/ is handling my save changes. If I use the standard EF savechanges it works, but not with the extensions version.
Disclaimer: I'm the owner of Entity Framework Extensions
It was indeed an issue in our library. This scenario was not yet supported for EF6.
However, starting from the v4.0.50, it should now work as expected.
I have couple of DTOs that I am using in WEB API and I have noticed that I am reusing same properties over and over again.
For example
public class Response
{
public int Id {get;set;}
public DateTime CreatedOn {get;set;}
public string Name {get;set;}
public string Code {get;set;}
}
public class InsertRequest
{
public string Name {get;set;}
public string Code {get;set;}
}
Is there really a need to specify InsertRequest for a resource, since DTOs are processed later in the code? It could be misleading to have property Id available in code even if Id would not be inserted.
On the other hand if Id is declared as nullable, it can be misleading since Id in Response should not be nullable, so I am in doubt should these two request be split or should they all be in one representing a resource?
The situation you describe is pretty common for situations where you have to create / update entities.
I would recommend you keep yours as they are. CreatedOn makes no sense when you want to update and Id, well, it'll never change once created, so again, it makes no sense to have it in a change entity. Plus, chances are that you will provide your Id in the route so the actual entity doesn't need it anyway:
PUT against:
www.somedomain.com/entity/id
entity doesn't need id field as its coming from the URI.
Yes, you can argue that you will end up with entities with duplicate fields, but at the same time you'll have a clean API which makes sense and that's more important as this is what the clients will be seeing and using.
One more thing, I am in favor of keeping a clear naming convention so if one entity is InsertRequest then the other will be UpdateRequest.
Consider this mock scenario where I have the following class:
public class Person {
public Guid Id {get;set;}
public string Name {get;set;}
public Address Address {get;set;}
}
I currently have a ReliableDictionary(Guid,Person). I'd like to change my Person model by splitting out the Address, to the following:
public class Person {
public Guid Id {get;set;}
public string Name {get;set;}
}
public class Address {
public Guid Id {get;set;}
public string Street {get;set;}
...
}
I would then have two ReliableDictionaries, one for persons and one for addresses.
What is the proper way to split these entities and migrate the data during an upgrade? I feel like there used to be documentation on this, but I can no longer find it.
Thanks!
An important requirement is that your model needs to be backward compatible. A stored model should be able to load, no matter the changes to your model.
Converting all stored models requires you to loop through the stored models, make changes and store them again. You need to write specific code for this. This could be included in the microservice, or you could create a temporary microservice which calls the microservice which holds the data. After the conversion is done you can remove the code or the microservice.
You could also do this 'on the fly'. Whenever a model is loaded during production you check the version number (you need to specify a version field on your model). If the version is below a certain value, convert it.
Given the Model:
Public Class Customer
Property Id() As Guid
Property FirstName() As String
Property MiddleName() As String
Property LastName() As String
Property Addresses() As ICollection(Of Address)
End Class
Public Class Address
Property Id() As Guid
Property Name() As String
Property Street() As String
Property City() As String
Property Zip() As String
Public Property Customer() As Customer
End Class
Entity Framework 6 Code First has created a column called Customer_Id in my table Addresses. Now, I'd like to add a Property Customer_Id to my class Address that represents the existing foreign key relation:
Public Class Address
Property Id() As Guid
Property Name() As String
Property Street() As String
Property City() As String
Property Zip() As String
Public Property Customer() As Customer
//Added
Public Property Customer_Id() As Guid
End Class
Unfortunately this results in an InvalidOperationException while creating the DbContext saying:
The model backing the 'DataContext' context has changed since the database was created.
I tried different property names (with and without underscore, different casing). But still no luck. So, what is the correct way to add those properties subsequently without the need for migrations? I assume it's possible, because the model does not really change, I am only changing from an implicit declaration of a property to an explicit...
Update:
The responses show me, that I did not explain the problem very well. After some more reading I found the correct names now: I have an application which is installed several times at customer locations (therefore dropping and recreating the database is no option). Currently, it depends on Entity Framework's Independent Associations, but I want to have the Foreign Key in my entity as well (this is no change to the model, the foreign key is already there, but does not exist as a property in my entity, since this is currently only relying on the IA instead). I did not manage to add it without EF thinking my Database is outdated.
for me two ways :
drop table __MigrationHistory : that is have the new model runs, but forget migration functionalities
create a new db by changing the connection string of the application. Replace old __MigrationHistory by __MigrationHistory of the newly created db
Never tested the second solution, but it should work.
Before using any solution:
backup you db.
Before using first solution: are you sure you will never need migration functionalities ?
This exception is because you change your model. You have to set migration strategy. Please look at:
http://msdn.microsoft.com/en-us/data/jj591621#enabling
(edited)
First of all you have to remove that exception. Even if you didn't add any new column to your database your model has changed because you added new property to Address class. If you check your DB you will find dbo.__MigrationHistory table with Model column. Last (earliest) value from that column is used for checking that your model and DB are compatible. I'm not sure but I think that EF stores there binary serialized model. So the solution is - recreate DB or add migration (probably empty migration).
(edited)
When you want to set FK you can do this very simple by Data Annotations
// c# example
public class Address
{
...
public string CustomerId { get; set; }
[ForeignKey("CustomerId")]
public Customer Customer { get; set; }
}
or in fluent api
// c# example
modelBuilder.Entity<Address>()
.HasRequired(arg => arg.Customer)
.WithMany()
.HasForeignKey(arg => arg.CustomerId);
or look at:
http://weblogs.asp.net/manavi/archive/2011/05/01/associations-in-ef-4-1-code-first-part-5-one-to-one-foreign-key-associations.aspx
http://msdn.microsoft.com/en-us/data/hh134698.aspx
I have a model as below:
public class Post
{
public int Id {get;set;}
public virtual ICollection<Comment> Comments {get;set;}
public virtual ICollection<Reader> Readers {get;set;}
public int Value {get;set;}
}
The rule is Value = Comments.Count * 2 + Readers.Count.
What is the right and convenient way to deal with the "computed" property of "Value"?
I think it is the best that the "Value" can be calculated and saved automatically when Comments or Readers add/remove element.
but the "DatabaseGeneratedAttribute" seems no use here.
Thank you!
This is not supported. There is no way to make Value available for linq-to-entities queries if it is not mapped to database column. In case of EF using EDMX for mapping this can be sometimes solved by using custom mapped SQL function or model defined function but code first mapping doesn't support anything of that. Other way is to create database view and map your entity to view but in such case entity will be read only.
Once you use .NET code for defining value it is always only client side property computed from data loaded from database. If you don't want to recompute property every time you need observable collections with event handler changing precomputed value each time the collection changes.
DatabaseGenerated attribute just marks your property as generated by database - in such case you cannot change its value and database must ensure that correct value will be stored in your table.
I think your column value is based on two mapped properties. Use NotMappedAttribute to Exclude a Property from the Database Schema and Load values in runtime.
public class Post
{
public int Id {get;set;}
public virtual ICollection<Comment> Comments {get;set;}
public virtual ICollection<Reader> Readers {get;set;}
[NotMapped]
public int Value
{
get return Comments.Count * 2 + Readers.Count;
}
}
You may use the DatabaseGenerated attribute and then create triggers in the db for calculating the Value. You can create the triggers in the migrations, or db seed method.