I have an application (webservice), where numerous objects are inserted into a database or updated (with Entity Framework), but never read. So I even don't have read-access to this database.
Now a new requirement wants some properties to be inserted but never to be updated. I save all objects like this:
Type t = obj.GetType();
db.Set<TT>().AddOrUpdate(MapObject<TT>(obj)); //Maps to database entity and saves object
Now the question is, if there is an attribute I can tell a property to be inserted, but ignored while updating? In best case something like this:
[InsertOnly]
public string SomeText { get; set; }
In your unit of work save routine, check ChangeTracker and remove those records or properties that are marked as update. Something like this:
var modifiedItems = _dbContext.ChangeTracker.Entries()
.Where(x => x.State == EntityState.Modified)
.ToList();
EntityState has following types:
Detached
Unchanged
Added
Deleted
Modified
You can add a custom attribute.
public class InsertOnly : Attribute
{}
If we consider
on your EF object, you add the custom property as such:
public class SomeEFClass {
[InsertOnly]
public int Id {get;set;}
}
Then change
db.Set<TT>().AddOrUpdate(MapObject<TT>(obj)); //Maps to database entity and
To include something like (This is pseudo code, don't expect it to run)
foreach(Property info pi in t.GetProperties())
{
if(pi.GetCustomAttribute<InsertOnly>() == null)
{
//You can safely update this property
}
else
{
//This property is only for inserting
}
}
I am uncertain if you can add the custom attribute via partial class overloading of a property? That might be worth a shot?
Related
I have a problem that I can't seem to understand, let alone solve. Any help would be appreciated.
So I have an MVC application, Entity Framework, the usual. I have a request that can update one property of one entity. This request sends the Id of the entity and the new value of the property.
In order to avoid querying the database for the entity, and since I only want to do the update on that one property and then return, I attach a new entity to the context with the id and then set the CurrentValue. Something like this:
public class MyEntityHandler
{
// dbContext is my EF context, instantiated from a DI container
// ...
public void UpdateProperty<TProp>(MyEntity entity, Expression<Func<MyEntity , TProp>> property, TProp value)
{
var memberName=(property.Body as MemberExpression).Member.Name;
var temp = dbContext.ChangeTracker.Entries<MyEntity>()
.SingleOrDefault( o => o.Entity.Id == entity.Id );
if( temp == null || temp.State == EntityState.Detached)
{
dbContext.MyEntities.Attach( entity );
}
this.dbContext.Entry(entity).Property(memberName).IsModified = true;
this.dbContext.Entry(entity).Property(memberName).CurrentValue = value;
}
}
Then, this can be used like this: UpdateProperty(e, e=>e.Prop, "NewValue");
where e is of type MyEntity, and Prop is a string property in it.
When the post request comes in, I simply create a new MyEntity, I assign the id and then call this method. Something like this (there are a couple more layers, but it makes no difference for my question):
public ActionResult MyMethod(int id, string newValue)
{
var e=new MyEntity { MyEntityId=id };
new MyEntityHandler().UpdateProperty(e,e=>e.Prop,newValue);
return View();
}
My understanding is that in this case, EF should attach the entity in unchanged state. The entity in my case is not in the context and the attach succeeds. Then I update that one single property, which will put the entity into Modified state, so an update statement should be generated on SaveChanges(). Since only the one property is changed, that should be the only one in the update statement. I double checked the values in the ChangeTracker, I can see that the entity is modified and the property is modified, but every other property i not modified.
The problem is that when I call SaveChanges(), I get a DbValidationException, because one of the other properties is null, but it has a Required attribute. This is all rightfully so - since I attach a new entity and set only the id before attaching, it should be null. I just don't understand the validation error - I'm not trying to insert that value into the database (again, I checked the ChangeTracker and the state of the entity is modified and the property in question is not modified).
Why do I get this validation error? Is this by design? Is there a way to tell EF to let this save through (without disabling the validation altogerher on the context)?
In my OData service I have to create a custom primary key in the OnPreInsert event handler.
I know I can't use #event.Id to assign the key because it doesn't expose the setter property.
I used the reflection to set the value of this property as shown below:
public bool OnPreInsert(PreInsertEvent #event)
{
if(#event.Entity is MyEnity)
{
var myEntity = #event.Entity as MyEnity;
string newKey = GetCustomKey(...);
myEntity.myId = newKey;
var property = typeof(AbstractPreDatabaseOperationEvent).GetProperty("Id");
if (property != null)
{
property.SetValue(#event,newKey);
}
}
return false;
}
During the debug mode I can see that the value of #event.Id is initialized properly, however the key saved in the database is not the one I generated in the OnPreInsert event handler.
What am I doing wrong here?
Please, try to check this recent Q&A:
NHibernate IPreUpdateEventListener, IPreInsertEventListener not saving to DB
The point is, that as described here:
NHibernate IPreUpdateEventListener & IPreInsertEventListener
...Here comes the subtlety, however. We cannot just update the entity state. The reason for that is quite simple, the entity state was extracted from the entity and place in the entity state, any change that we make to the entity state would not be reflected in the entity itself. That may cause the database row and the entity instance to go out of sync, and make cause a whole bunch of really nasty problems that you wouldn’t know where to begin debugging.
You have to update both the entity and the entity state in these two event listeners (this is not necessarily the case in other listeners, by the way). Here is a simple example of using these event listeners...
I couldn't find some way to use the reflection to achieve what I described in my question above. I tried to use reflection because I didn't know about the Generators available in NHibernate (as I am new to NHibernate).
I have a table named sys_params which holds the next key values for different tables. My target was to fetch the next key for my table my_entity, assign it to the primary key of the new record, increment the next key value in the sys_params table and save the new record into the database.
To achieve this first I defined following classes.
public class NextIdGenerator : TableGenerator
{
}
public class NextIdGeneratorDef : IGeneratorDef
{
public string Class
{
get { return typeof(NextIdGenerator).AssemblyQualifiedName; }
}
public object Params
{
get { return null; }
}
public Type DefaultReturnType
{
get { return typeof(int); }
}
public bool SupportedAsCollectionElementId
{
get { return true; }
}
}
And then in my mapping class I defined the generator like below:
public class MyEnityMap : ClassMapping<MyEnity>
{
public MyEnityMap()
{
Table("my_entity");
Id(p => p.myId,
m=>{
m.Column("my_id");
m.Generator(new NextIdGeneratorDef(), g =>g.Params( new
{
table = "sys_params",
column = "param_nextvalue",
where = "table_name = 'my_entity'"
}));
});
.......
}
}
Hope this will help someone else. Improvements to this solution are highly appreciated.
I'm using EF5 database first with partial classes. There's a property in my partial class which contains n object which is stored as a column in my database containing XML data. I want to handle the serialization/deserialization of this object when the EF tries to read/write it with a custom getter/setter.
Is it possible to expose the column in my partial class and map it using the EF, without auto-generating a property for it?
ie:
public SomeObject BigComplexObject { get; set; } // forms etc in my app use this
public string BigComplexObjectString // when the EF tries to read/write the column, my custom getter/setter kicks in
{
get { return this.BigComplexObject.ToXmlString(); }
set { this.BigComplexObject = new BigComplexObject(value); }
}
At present, the EF is auto-generating a member for the column so I'm left with two.
Try to change the logic. Leave EF generated property that will be populated with XML string from the database:
public string BigComplexObjectString { get; set; }
Then do the following:
[NotMapped]
public SomeObject BigComplexObject
{
get { return new SomeObject(this.BigComplexObjectString); }
set { this.BigComplexObjectString = value.ToXmlString(); }
}
Don't forget to add [NotMapped] to instruct EF to ignore this property.
Well, we use a little trick for a quite similar case...
We use the property panel (in the edmx file) of our... properties and add something in the "documentation" (summary or long description) line (probably not the best place, but anyway). This can be access by your T4 file.
So you could write something like "useXml" in the property panel, then modify your tt to generate the desired code when (example to get the info in the .tt file)
if (edmProperty.Documentation != null && edmProperty.Documentation.Summary = "useXml")
//generate something special
It would be great to have a better place for "cusom infos" in the edmx, but we didn't find anything better for instant.
I have a problem with my code where I try to save a many to many connection between two objects, but for some reason it doesn't get saved.
We used the code first method to create our database, in our database we have the following entities where this problem is about:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ProductTag> ProductTags { get; set; }
}
public class ProductTag
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
The table ProductTagProducts got automatically created, which is of course just a connection table between the two.
Now creating products works fine. We can just run the following and it will create the connnections in the ProductTagProducts table:
Product.ProductTags.Add(productTag);
To make sure no duplicate tasks are in the database, we handle the saving for it ourselves. The productTag always contains a product tag with an existing ID.
The problem occurs when we want to edit the same or another product. There are existing tags for the product. And we use the following process to save it:
List<ProductTag> productTags = new List<ProductTag>();
string[] splittedTags = productLanguagePost.TagList.Split(',');
foreach (string tag in splittedTags) {
ProductTag productTag = new ProductTag();
productTag.Name = tag;
productTags.Add(productTagRepository.InsertAndOrUse(productTag));
}
We split the tags by comma, that's how it is received from the HTML element. Then we define a new entity for it and use InsertAndOrUse to determine if the tag already existed. If the tag already existed, it returns the same entity but with the ID filled in, if it did not exist yet it adds the tag to the database, and then also returns the entity with ID. We create a new list to be sure that the product doesn't have duplicate Id's in there (I have tried it with adding it to the product's existing tag list directly, same result).
product.ProductTags = productTags;
productRepository.InsertOrUpdate(product);
productRepository.Save();
Then we set the list to ProductTags and let the repository handle the insert or update, of course, an update will be done. Just in case, this is the InsertOrUpdate function:
public void InsertOrUpdate(Product product) {
if (product.Id == default(int)) {
context.Products.Add(product);
} else {
context.Entry(product).State = EntityState.Modified;
}
}
The save method just calls the context's SaveChanges method. When I edit the product, and add another tag it doesn't save the new tag. However, when I set a breakpoint on the save function I can see that they are both there:
And when I open the newly added tag 'Oeh-la-la' I can even refer back to the product through it:
But when the save happens, which succeeds with all other values, there are no connections made in the ProductTagProducts table. Maybe it is something really simple, but I am clueless at the moment. I really hope that someone else can give a bright look.
Thanks in advance.
Edit: As requested the ProductTag's InsertAndOrUse method. The InsertOrUpdate method it calls is exactly the same as above.
public ProductTag InsertAndOrUse(ProductTag productTag)
{
ProductTag resultingdProductTag = context.ProductTags.FirstOrDefault(t => t.Name.ToLower() == productTag.Name.ToLower());
if (resultingdProductTag != null)
{
return resultingdProductTag;
}
else
{
this.InsertOrUpdate(productTag);
this.Save();
return productTag;
}
}
You have to know that this line...
context.Entry(product).State = EntityState.Modified;
...has no effect on the state of a relationship. It just marks the entity product being passed into Entry as Modified, i.e. the scalar property Product.Name is marked as modified and nothing else. The SQL UPDATE statement that is sent to the database just updates the Name property. It doesn't write anything into the many-to-many link table.
The only situation where you can change relationships with that line are foreign key associations, i.e. associations that have a foreign key exposed as property in the model.
Now, many-to-many relationships are never foreign key associations because you cannot expose a foreign key in your model since the foreign keys are in the link table that doesn't have a corresponding entity in your model. Many-to-many relationships are always independent associations.
Aside from direct manipulations of relationship state entries (which is rather advanced and requires to go down to the ObjectContext) independent associations can only be added or deleted using Entity Framework's change tracking. Moreover you have to take into account that a tag could have been removed by the user which requires that a relationship entry in the link table must be deleted. To track such a change you must load all existing related tags for the given product from the database first.
To put all this together you will have to change the InsertOrUpdate method (or introduce a new specialized method):
public void InsertOrUpdate(Product product) {
if (product.Id == default(int)) {
context.Products.Add(product);
} else {
var productInDb = context.Products.Include(p => p.ProductTags)
.SingleOrDefault(p => p.Id == product.Id);
if (productInDb != null) {
// To take changes of scalar properties like Name into account...
context.Entry(productInDb).CurrentValues.SetValues(product);
// Delete relationship
foreach (var tagInDb in productInDb.ProductTags.ToList())
if (!product.ProductTags.Any(t => t.Id == tagInDb.Id))
productInDb.ProductTags.Remove(tagInDb);
// Add relationship
foreach (var tag in product.ProductTags)
if (!productInDb.ProductTags.Any(t => t.Id == tag.Id)) {
var tagInDb = context.ProductTags.Find(tag.Id);
if (tagInDb != null)
productInDb.ProductTags.Add(tagInDb);
}
}
}
I was using Find in the code above because I am not sure from your code snippets (the exact code of InsertAndOrUse is missing) if the tags in the product.ProductTags collection are attached to the context instance or not. By using Find it should work no matter if the they are attached or not, potentially at the expense of a database roundtrip to load a tag.
If all tags in product.ProductTags are attached you can replace ...
var tagInDb = context.ProductTags.Find(tag.Id);
if (tagInDb != null)
productInDb.ProductTags.Add(tagInDb);
... just by
productInDb.ProductTags.Add(tag);
Or if it's not guaranteed that they are all attached and you want to avoid the roundtrip to the database (because you know for sure that the tags at least exist in the database, if attached or not) you can replace the code with:
var tagInDb = context.ProductTags.Local
.SingleOrDefault(t => t.Id == tag.Id);
if (tagInDb == null) {
tagInDb = tag;
context.ProductTags.Attach(tagInDb);
}
productInDb.ProductTags.Add(tagInDb);
I'm working with a database that due to reason out of my control - I cannot modify the schema.
This database has a field "CertificateId" that is non-nullable, however, the field is still considered to be optional. When I load this field into my model (I'm using the DB first approach), it is of course tagged as being non-nullable, as you can see in the designer.cs
[EdmScalarPropertyAttribute(EntityKeyProperty = false, IsNullable = false)]
[DataMemberAttribute()]
public global::System.String CertificateId
{
get
{
return _CertificateId;
}
set
{
OnCertificateIdChanging(value);
ReportPropertyChanging("CertificateId");
_CertificateId = StructuralObject.SetValidValue(value, false);
ReportPropertyChanged("CertificateId");
OnCertificateIdChanged();
}
}
Since this field is optional, it gets passed null sometimes, which obviously fails validation.
Is there any way of overloading these properties that are autogenerated? I would like to be able to check if the value passed into the set property is null, and if it is, set it to an empty string before it goes into validation.
Or, is it possible to override the metadata for this property and have IsNullable set to true?
First of all if the field is considered as not nullable you mustn't set its value to null. Once you manually assign null in your code it's your bug.
Another problem which have to be solved is default value (null) if you don't assign the value. This is well discussed in this question - I like the way with initializing the field with constructor.
If for any reason previous two options are not what you want you have a last choice of modifying the value in overriden SaveChanges. Something like:
public override int SaveChanges(SaveOptions options)
{
var data = context.ObjectStateManager
.GetObjectStateEntries(EntityState.Added | EntityState.Modified)
.Where(e => !e.IsRelationship)
.Select(e => e.Entity)
.OfType(MyEntity);
foreach(var entity in data)
{
if (entity.CertificateId == null)
{
entity.CertificateId = String.Empty;
}
}
return base.SaveChanges(options);
}
If You created your model classes with a designer, then you can manually set this property to be nullable in your model.
If your field is optional, why not make the database column nullable, instead of putting in empty string?
You could use poco's, or you could update the existing entity framework template file (T4 templates), so that you can add your null check within the template, and when the entity framework classes are generated is uses your template. I'll try and find you some links I've used in the past.
Here's information on using POCO's
http://msdn.microsoft.com/en-us/library/dd456853.aspx
Another way to handle this is to create a stored procedure, add that to your model, and handle your logic to make nulls empty strings within the sp.
Thanks guys, I simply used the constructor in a partial class to set my default:
public partial class Emp_Certificate
{
//Constructor
public Emp_Certificate()
{
this.CertificateID = "";
}
}