CreatedDate and AmendDate generated by the database - entity-framework-4.1

User class:
public class User
{
public DateTime CreatedDate { get; private set; }
public DateTime? AmendDate { get; private set; }
}
I need the columns CreatedDate and AmendDate have the values ​​generated by the database (now() command)
Reading some articles on the internet, advised me to use this in DbContext class on OnModelCreating method:
Property(p => p.CreatedDate)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(p => p.AmendDate)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
But does not work, saying that an error is supported types are rowversion and timespan.
How do you do with these property types?
Any tips?

You can make both columns nullable and Save changes with both columns with null values. You also need set the default value for the column as getdate() in the relevant column in the database. Then these columns will automatically be populated with current date if you try to insert null values. But EF will not update the property with the database generated date value.
Other solution is to set the values before you call the SaveChanges() method
public override int SaveChanges()
{
var changeSet = ChangeTracker.Entries<User>();
if (changeSet != null)
{
foreach (var entry in changeSet
.Where(c => c.State == EntityState.Added || c.State == EntityState.Modified))
{
entry.Entity.CreatedDate = entry.Entity.AmendDate = DateTime.Now;
}
}
return base.SaveChanges();
}

Related

Sum in child collection

Using MySQL with EF6 throws an exception when I sum values from an empty child collection as the DefaultIfEmpty is not supported well with MySQL as related in bug #80127.
public class Foo
{
public int Id { get; set; }
public decimal Total { get; set; }
public virtual IList<Bar> Bars { get; set; }
}
public class Bar
{
public int Id { get; set; }
public int FooId { get; set; }
public virtual Foo Foo { get; set; }
public decimal Received { get; set; }
}
Using the recommended approach with DefaultIfEmpty throws an exception with invalid where clausule 'Project1'.'Id'. This is an old bug of MySQL.
var result = db.Foo.Select(f => new {
Total = f.Total,
Received = f.Bars.Select(b => b.Received).DefaultIfEmpty().Sum()
});
I'm using an inline if that works fine but generates an very ugly SQL with lots of inner queries and repetitions of select statements.
var result = db.Foo.Select(f => new {
Total = f.Total,
Received = f.Bars.Any() ? f.Bars.Sum(b => b.Received) : 0
});
Is there a better way to avoid DefaultIfEmpty?
The alternative of DefaultIfEmpty which I usually prefer is using cast operator to promote the non nullable type to nullable, which works (even) with MySQL connector.
Then the solution depends of your receiver class property type.
The best is if you can receive a nullable result, in which case the query is simple:
var result = db.Foo.Select(f => new {
Total = f.Total,
Received = f.Bars.Sum(b => (decimal?)b.Received)
});
If it needs to be a non nullable type, you can use null coalescing operator
var result = db.Foo.Select(f => new {
Total = f.Total,
Received = f.Bars.Sum(b => (decimal?)b.Received) ?? 0
});
but the generated SQL query is ugly and inefficient.
The best you can do in such case is to use (a quite annoying) double select trick:
var result = db.Foo.Select(f => new {
f.Total,
Received = f.Bars.Sum(b => (decimal?)b.Received)
})
.Select(r => new {
r.Total,
Received = r.Received ?? 0
};
or (a quite better) query syntax with let clause:
var result =
from f in db.Foos
let received = f.Bars.Sum(b => (decimal?)b.Received)
select new { f.Total, Received = received ?? 0 };
Tested on latest EF6.1.3 with MySQL Connector/Net 6.9.8

How to update an Entity in Entity Framework

I have a Date field in my DB and I'm trying to update it to the current Date when I press the submit button on my webpage but it does not update. I believe I'm doing the correct steps but here is my code.
Controller:
public ActionResult TakeInventory(int? AssetNum, string owners, string locationId, string clientId)
{
ViewBag.LocationId = new SelectList(db.Locations, "LocationKey", "LocationName");
ViewBag.ClientId = new SelectList(db.ClientSites, "ClientSiteKey", "ClientSiteName");
var records = from s in db.Assets select s;
if (AssetNum != 0)
{
records = records.Where(c => c.AssetKey == AssetNum);
}
if (!String.IsNullOrEmpty(owners))
{
records = records.Where(x => x.InventoryOwner.Equals(owners));
}
if (!String.IsNullOrEmpty(locationId))
{
int locnum = Convert.ToInt32(locationId);
records = records.Where(x => x.LocationKey == locnum);
}
if (!String.IsNullOrEmpty(clientId))
{
int clinum = Convert.ToInt32(clientId);
records = records.Where(x => x.ClientSiteKey == clinum);
}
else
{
return View(records);
}
return View(records);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult TakeInventory([Bind(Include = "InventoryDate")] Asset asset)
{
if (ModelState.IsValid)
{
db.Entry(asset).State = EntityState.Modified;
asset.InventoryDate = DateTime.Now;
db.Assets.Add(asset);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(asset);
}
View:
#foreach (var items in Model)
{
<p>Last Inventory Date: #Html.DisplayFor(modelItem => items.InventoryDate) </p>
<input type="submit" value="Submit" />
Model:
public partial class Asset
{
public System.DateTime InventoryDate { get; set; }
public Asset()
{
InventoryDate = DateTime.Now;
}
}
You want to retrieve the Asset entity again before updating again.
For example,
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult TakeInventory([Bind(Include = "InventoryDate")] Asset asset)
{
if (ModelState.IsValid)
{
var entity = (from s in db.Assets where AssetNum == asset.AssetNum Select s).FirstOrDefalt();
entity.InventoryDate = DateTime.Now;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(asset);
}
Is a bad practice:
asset.InventoryDate = DateTime.Now;
At least, you need:
1. SaveChanges() your DbContext
2. Your DateTime field in backend must be Nullable or NotNull in Db (there is no inmpicit conversion)
But the real trouble is timezones. Its all works fine, if you have only one instance in only one datacenter and all your clients is from only one small and beauty country (one timezone wide)
DateTime.Now returns you local mashine timezone time.
If you use your 'entity.InventoryDate' in any kind of requests query it can return confused rezults, and can be surprized with funny result: for ex., value with tomorrow datetime relatively to you :)
For Web-services always cast to UTC that kind of fields, or use triggers or default expression for this kind of fields inside your DB engine
P.S. Russia is 11 timezones wide, i know what i'm talking about
Why you are passing the Current date , there is no need for that you can you Sql build in function "GETDATE()" to Get the current Date

Change item queue in #Html.EnumDropDownListFor

There's this enum
public enum UserStatus
{
Employee = 0,
Evaluation,
Dismissed,
Registered,
}
And on view
#Html.EnumDropDownListFor(model => model.User.Metadata.Status)
So it show me Employee as default option and all other items with enum queue (E,E,D,R). But i'd like to show items in this queue (Evaluation, Registered, Employee, Dismissed) (Mainly Evaluation must be first).
I cant change the enum, and i cant set as default in GET controller (due to model realization).
Any ideas how solve this problem?
I don't think you can change the list during runtime on how it appears. The easiest way i can think to handle such problem ( where you can't change the sequence in which the enum values appear ) is to add attribute on each enum value that defines the sequence number and then extract all the items in a specific enum to create a list which can be binded to the view. This might be an extra work but would solve your issue.
Here's a sample code :
public class Sequence : Attribute
{
public int SequenceNum { get; set; }
}
public enum UserStatus
{
[Sequence(SequenceNum=3)]
Employee = 0,
[Sequence(SequenceNum = 2)]
Evaluation,
[Sequence(SequenceNum = 4)]
Dismissed,
[Sequence(SequenceNum = 1)]
Registered,
}
In your model class :
public IEnumerable<SelectListItem> ListData { get; set; }
public UserStatus Status { get; set; }
In your controller :
List<KeyValuePair<int,string>> KeyValueList = new List<KeyValuePair<int,string>>();
// Get all the enum values
var values = Enum.GetValues(typeof(UserStatus)).Cast<UserStatus>();
// Iterate through each enum value to create a keyvalue list
foreach (var item in values)
{
var type = typeof(UserStatus);
var memInfo = type.GetMember(item.ToString());
var attributes = memInfo[0].GetCustomAttributes(typeof(Sequence),false);
KeyValueList.Add(new KeyValuePair<int,string>(((Sequence)attributes[0]).SequenceNum,item.ToString()));
}
// Sort the keyvalue list based on the *SequenceNum* attribute
KeyValueList.Sort((firstPair, nextPair) =>
{
return nextPair.Value.CompareTo(firstPair.Value);
});
// Add SelectListItem collection to a model property - Apparently you can add the generate collection to a ViewData ( if you don't wish to create another property )
model.ListData = KeyValueList.Select(x => new SelectListItem { Value = x.Key.ToString(), Text = x.Value }).ToList();
In your View :
#Html.DropDownListFor(m => m.Status, Model.ListData));
When you post the data, the model property Status would be populated with the selected Enum value.
Hope it helps.

Fluent NHibernate, SQL Server and string length specification not working as expected

I am following the Summer of NHibernate tutorials but I am not using the xml mappings but instead, I am making use of Fluent NHibernate to do the mappings.
My Customer entity class is:
public class Customer
{
public virtual int CustomerId { get; set; }
public virtual string Firstname { get; set; }
public virtual string Lastname { get; set; }
}
The corresponding mapping class is:
public class CustomerMap: ClassMap<Customer>
{
public CustomerMap()
{
Id(x =>x.CustomerId);
Map(x => x.Firstname).Length(50).Nullable();
Map(x => x.Lastname).Length(50).Nullable();
ImportType<CustomerFirstnameCounter>();
}
}
My DAO class is:
public int AddCustomer( Customer customer )
{
using( ISession session = GetSession() )
{
using( ITransaction tx = session.BeginTransaction() )
{
try
{
int newId = ( int ) session.Save( customer );
session.Flush();
tx.Commit();
return newId;
}
catch( GenericADOException )
{
tx.Rollback();
throw;
}
}
}
}
And finally my test is:
[Test]
public void AddCustomerThrowsExceptionOnFail()
{
// Arrange
Customer customer = BuildInvalidCustomer();
// Act
_provider.AddCustomer( customer );
// Assert
}
When the test runs, no exception is thrown! So my first question is whether anyone can see what is wrong with my mapping.
Now, in the dB, the Firstname field is set as a varchar(50). When I debug the test, I see that the data is inserted but truncated (I do get warning messages). So this might indicate
that I haven't set the dB up properly. Can anyone point me in the direction of where to prevent this truncation of data in SQL Server?
This answer should help you.
I will also use Data Annotation StringLengthAttribute to ensure validation of your properties
.Length(50) does not check lengths at run-time. This is only used if you are generating the database schema from the mappings.
If you wish to validate the length of values you will have to either do this manually or use some validation framework like NHibernate Validator

Using database default values with Linq to SQL codewise

I am using Dynamic Data with linq to SQL and SQL Server 2008.
I have a GUID column that gets his value from the default value with newguid(). When I set IsDbGenerated to true in the designer it works like a charm.
But when I renew table this property is set back to false again. So I added it to the metadata. For some reason it's not being pickup, "00000000-0000-0000-0000-000000000000" is being inserted in database. The displayname and readonly change are being pick up.
What am I missing?
[MetadataType(typeof(CMS_Data_HistoryMetadata))]
public partial class CMS_Data_History
{
}
[TableName("Content")]
public class CMS_Data_HistoryMetadata
{
[DisplayName("Pagina Title")]
public object pageTitleBar { get; set; }
[ReadOnly(true)]
[DisplayName("Versie")]
public object version_date { get; set; }
[ColumnAttribute(IsDbGenerated = true)]
public object entity_id;
}
I solved the problem by extending the partial insert en update class and check there if the guid is filled
partial void
InsertCMS_Data_History(CMS_Data_History
instance)
{
if(instance.entity_id == Guid.Empty)
{
instance.entity_id = Guid.NewGuid();
}
this.ExecuteDynamicInsert(instance);
}
partial void UpdateCMS_Data_History(CMS_Data_History
instance)
{
if (instance.version_date == DateTime.MinValue)
{
instance.version_date = DateTime.Now;
}
this.ExecuteDynamicUpdate(instance);
}