i have a code-first project where one of the POCOs contains a decimal field which is being setup in the database as this:
rate = c.Decimal(nullable: false, precision: 18, scale: 4),
i can see in the SQL database it reflecting correct precision and scale. i need it to store scale of up to 4 digits, i.e. anything bigger than 0.0001 - however, what is happening when persisting values is - everything beyond first two digits of the scale is persisted as 0. i.e. - 0.0100 would be persisted as 0.0100 however - 0.0090, 0.0050 or 0.0010 would all be persisted as 0.0000. i am probably overlooking some obvious aspect here but i have been looking for any documentation reference and don't seem to be finding anything so far, i'd appreciate any clues on this. thank you!
i have found the answer here and it seems to have helped me resolve the issue. Sam talks exactly the same symptoms i was facing. re-doing steps Sam recommended seem to have resolve the issue. here is the link:
[Sam's article on the issue][1]https://storiknow.com/entity-framework-decimal-scale-and-precision-convention/
having implemented steps in Sam's article, it caused a side issue with the context creation, and i had the following errors pop-up as exception during migration add:
DatabaseAccess.databaseContext.ApplicationUserRole: : EntityType 'ApplicationUserRole' has no key defined. Define the key for this EntityType.
DatabaseAccess.databaseContext.ApplicationUserLogin: : EntityType 'ApplicationUserLogin' has no key defined. Define the key for this EntityType.
which in my case was solved by adding the following lines to OnModelCreating():
modelBuilder.Entity<ApplicationUserRole>().ToTable("AspNetUserRoles").HasKey(ur => new { ur.RoleId, ur.UserId });
modelBuilder.Entity<ApplicationUserLogin>().ToTable("AspNetUserLogins").HasKey<int>(ul => ul.UserId);
here, the overall OnModelCreating() definition:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Add(new decimalPrecisionAttributeConvention());
modelBuilder.Entity<ApplicationUserRole>().ToTable("AspNetUserRoles").HasKey(ur => new { ur.RoleId, ur.UserId });
modelBuilder.Entity<ApplicationUserLogin>().ToTable("AspNetUserLogins").HasKey<int>(ul => ul.UserId);
}
Just in case someone stumbles against the same issue.
Related
I am using EF 6.1.3. Using code first sets a byte[] property in an entity to max. 8000 bytes. Any attempt to make it greater, that is MAX, fails.
HasMaxLength(null) (yes, the parameter is int?) still sets it to 8000, HasMaxLength(int.MaxValue) or any other value greater than 8000 makes EF throw System.Data.Entity.Core.MetadataException:
Schema specified is not valid. Errors: (0,0) : error 0026: MaxLength
'2147483647' is not valid. Length must be between '1' and '8000' for
'varbinary' type.
SQL server 13.0.2151 (mssqllocaldb) allows for varbinary(max):
This limit seems too severe to me. Trying to find a reason why it is imposed does not yield a good reason for this too. So, my question is
How a byte[] can be mapped to varbinary(max) in EF code first?
PS: The property is also 'required', but I am not sure if an optional property may be set to varbinary(MAX) either. Anyway, i have not tested this case since it does not make much sense to me.
Despite the multiple articles that states the solution is to add the following attribute
[Column(TypeName="image")]
byte[] Photo { get; set; }
I found the correct approach to be, adding instead this attribute
[MaxLength]
public byte[] Photo { get; set; }
With the Column(TypeName) recommendation I'll end up getting the following error with SQLCE:
The field Photo must be a string or array type with a maximum length of '4000'
Well, I found a workaround to this. Specifying HasColumnType("image") solves the problem, but I still think that EF must allow for specifying varbinary(max) as well.
Moreover, not all binary files are images. ;)
And still part of the question remains unanswered, so I will put it this way:
Why a byte[] property cannot be mapped to varbinary(max) in EF code first?
Any comments (or answers of course) are welcome. Thanks in advance.
EDIT (as per comment by Gert): leaving the property without any specs makes EF generate varbinary(max). Surprisingly simple!
It is possible.
Fluent API
.IsMaxLength()
Before you want to update the database take a look in the filename which is generated after you use "add-migration filename"
If you see a method "CreateTable" and see that a field which should te be a binary type with a lenght of MAX, it can be generated as c.Binary(maxLength: 8000), remove the parameter maxLength at all and then use update-database and after that you can check the created table in the SQL server database!
I've come across the craziest behaviour in an application. I am using EF 6 and have a model with a decimal property, the database type powering is a decimal (18,4). If I change the value of the decimal from 0.6500 to 0.6550 and do save changes on the context, the row is not updated. If I change it from 0.6500 to 0.1350 and save changes, the row will get updated but the value is saved as 0.1300 so it has lost the 0.005. I know the database can hold that precision as it currently does for some manually inserted data, and EF retrieves that without issue.
What on earth do I need to do to get EF to update my row/maintain precision. Help much appreciated, I might go cry.
So I found the answer to my own question after having left the issue for a while. It would seem EF default precision for decimals is 2, I can override this myself and set it to 4 as I want. I stole the code from this previously answered question:
Set decimal(16, 3) for a column in Code First Approach in EF4.3
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<MyClass>().Property(x => x.SnachCount).HasPrecision(16, 3);
modelBuilder.Entity<MyClass>().Property(x => x.MinimumStock).HasPrecision(16, 3);
modelBuilder.Entity<MyClass>().Property(x => x.MaximumStock).HasPrecision(16, 3);
}
This is my model:
- Business
- BusinesType - FK
- Categories (*) - FK
- Branch (*)
- BranchType - FK
- Address
- Phone (*)
- CustomFields (*)
- OpeningTimes (*)
- WorkingPeriods (*)
- .....
Now I have a controller-action that accepts a form that consists of the whole bunch of data as a single Business entity with all its properties and collections set fine.
Now I have to walk thru all the properties and collections recursively, and compare with the database graph; if they don't exist, add them, if they do walk thru all properties again and perform the same to a deeper level until no navigation properties are left over. Since I have many more properties and descendants than mentioned in the previous example, it's just inside to walk thru them manually.
Thanks to this answer I found GraphDiff which offered a brilliant solution to the situation.
Here's an update query I'm calling:
Context.UpdateGraph(business, bus => bus
.AssociatedEntity(bu => bu.BusinessType)
.AssociatedCollection(bu => bu.Categories)
.OwnedCollection(bu => bu.Branches, branch => branch
.AssociatedEntity(b => b.BranchType)
.OwnedEntity(b => b.Address)
.OwnedCollection(b => b.Phones)
.OwnedCollection(b => b.CustomFields)
.OwnedCollection(b => b.OpeningTimes, openingTimes => openingTimes
.OwnedCollection(b => b.WorkingPeriods)
)
)
);
It throws this exception:
System.InvalidCastException: Unable to cast object of type 'System.Linq.Expressions.MethodCallExpressionN' to type 'System.Linq.Expressions.MemberExpression'.
I tried debugging the source code, but I'm not an expert with Expression Trees, the problem occurs when the internal Include call (to include object graph to load store object) tries to attach WorkingPeriods, looks like it's not ready to take that deepness level of recursion. I messed around with it a bit, but I'm sure someone with extensive knowledge in expression trees will be able to solve this easily. Any suggestions will be appreciated on that to.
Here's what the include path expression is supposed to be generated like:
.Include(b =>
b.Branches.Select(br =>
br.OpeningTimes.Select(ot =>
ot.WorkingPeriods)));
Here's the stacktrace of the error.
Essentially, the exception is thrown because the recursive call returns the inner include as a method call, without processing it and returning the collection property it's meant to expose.
sorry it took me a while to get back to you.
I'ts 3am and I've had a fair bit of wine but the problem is fixed :) If you get the latest version of code # https://github.com/refactorthis/GraphDiff it should work fine.
I'll update the new nuget package (RefactorThis.GraphDiff) soon.
Haven't seen this error before, and a cursory web search turns up very little. Here's (I think) the offending code:
this.HasMany(a => a.ListItems).WithRequired()
.Map(m =>
{
m.MapKey("AttributeId");
m.ToTable("ProductAttributeListItem");
}
)
;
And here's the full error:
The specified table 'ProductAttributeListItem' was not found in the
model. Ensure that the table name has been correctly specified.
The table is there and spelled correctly.
The lack of search results makes me think I'm missing something obvious. What might that be?
If you want to define the table name of the entity ListItems is refering to you need to do that on the entity, not in the relationship mapping:
modelBuilder.Entity<ListItem>() // or whatever the entity is called
.ToTable("ProductAttributeListItem");
And remove m.ToTable from the Map action.
i seem to only be having this issue with 1 particular model when i am searching by ID
>> Cart.where(:_id => '4dae5902e1607c232c000009').first
=> #<Cart _id: 4dae5902e1607c232c000009, _id: BSON::ObjectId('4dae5902e1607c232c000009'), _type: nil>
>> Cart.find('4dae5902e1607c232c000009')
Mongoid::Errors::DocumentNotFound: Document not found for class Cart with id(s) 4dae5902e1607c232c000009.
the strange thing is that with other models, i can use find just fine. any ideas?
the rest of the stack is...
from /Library/Ruby/Gems/1.8/gems/mongoid-2.0.1/lib/mongoid/criterion/inclusion.rb:192:in `execute_or_raise'
from /Library/Ruby/Gems/1.8/gems/mongoid-2.0.1/lib/mongoid/criterion/inclusion.rb:190:in `tap'
from /Library/Ruby/Gems/1.8/gems/mongoid-2.0.1/lib/mongoid/criterion/inclusion.rb:190:in `execute_or_raise'
from /Library/Ruby/Gems/1.8/gems/mongoid-2.0.1/lib/mongoid/criterion/inclusion.rb:106:in `find'
from /Library/Ruby/Gems/1.8/gems/mongoid-2.0.1/lib/mongoid/finders.rb:67:in `find'
from (irb):37
Normally the issue is the other way around. With the where failing and the find working.
That is cause by where not casting the id into BSON::ObjectId before the query.
Usually you would have to do this
Cart.where(:_id => BSON::ObjectId('4dae5902e1607c232c000009')).first
This leads me to believe that your ids are stored as strings and not BSON:ObjectId and would explain why find fails ( it is searching for a BSON::ObjectId not a string)
Could also explain why it is only one model as it depends entirely on how the objects are stored.
Hope that helps