i've a problem whit breeze returned DateTime... i've tried also to update BreezeJs to the latest version but nothing change. I use breezeJs with HotTowel SPA
Controller:
[BreezeController]
public class ContribuentiController : ApiController
{
readonly EFContextProvider<LarksTribContext> _contextProvider =
new EFContextProvider<LarksTribContext>();
[System.Web.Http.HttpGet]
public string Metadata()
{
return _contextProvider.Metadata();
}
// ~/api/todos/Todos
// ~/api/todos/Todos?$filter=IsArchived eq false&$orderby=CreatedAt
[System.Web.Http.HttpGet]
public IQueryable<Contribuente> Contribuenti()
{
if (_contextProvider.Context.Contribuente != null)
{
return _contextProvider.Context.Contribuente.Include("Residenze.Strada");//.Include("Residenze").Include("Residenze.Strada");
}
else
{
return null;
}
}
[System.Web.Http.HttpPost]
public SaveResult SaveChanges(JObject saveBundle)
{
return _contextProvider.SaveChanges(saveBundle);
}
}
Model:
[Table(name: "Contribuenti")]
public class Contribuente
{
[Key]
public int Id { get; set; }
[MaxLength(30,ErrorMessage = "Il cognome non deve superare i 30 caratteri")]
public string Cognome { get; set; }
[MaxLength(35, ErrorMessage = "Il nome non deve superare i 35 caratteri")]
public string Nome { get; set; }
[MaxLength(16, ErrorMessage = "Il Codice fiscale non deve superare i 16 caratteri")]
public string CodiceFiscale { get; set; }
public virtual ICollection<Residenza> Residenze { get; set; }
}
[Table(name: "Residenze")]
public class Residenza
{
[Key, Column(Order = 0)]
public int Id { get; set; }
public int ContribuenteId { get; set; }
[ForeignKey("ContribuenteId")]
public Contribuente Contribuente { get; set; }
public DateTime? DataInizio { get; set; }
public int StradaId { get; set; }
[ForeignKey("StradaId")]
public Strada Strada { get; set; }
public int Civico { get; set; }
public string Interno { get; set; }
public string Lettera { get; set; }
}
[Table(name: "Strade")]
public class Strada
{
[Key]
public int Id { get; set; }
[MaxLength(20,ErrorMessage = "Il toponimo deve contenere al massimo 20 caratteri")]
public string Toponimo { get; set; }
[MaxLength(50, ErrorMessage = "Il nome deve contenere al massimo 50 caratteri")]
public string Nome { get; set; }
}
when i make this query:
var query = breeze.EntityQuery.
from("Contribuenti").expand(["Residenze"], ["Strada"]);
the json response is:
[{"$id":"1","$type":"LarksTribUnico.Models.Contribuente, LarksTribUnico","Id":1,"Cognome":"Manuele","Nome":"Pagliarani","CodiceFiscale":"HSDJSHDKHSD","Residenze":[{"$id":"2","$type":"LarksTribUnico.Models.Residenza, LarksTribUnico","Id":5,"ContribuenteId":1,"Contribuente":{"$ref":"1"},"DataInizio":"2012-12-10T22.00.00.000","StradaId":4,"Strada":{"$id":"3","$type":"LarksTribUnico.Models.Strada, LarksTribUnico","Id":4,"Toponimo":"Via","Nome":"Milano"},"Civico":0}]}]
But in result of query "DataInizio" is always marked as "Invalid date".
Any idea aout the problem?
Thanks
Breeze server side converts SQL Server DateTime to ISO 8601. In my code (breeze v0.72) dates seem to end up in UTC in SQL, and get converted back to local somewhere in breeze.
Check the Breeze docs on dates. http://www.breezejs.com/documentation/date-time
or, as suggested in the breeze docs, you can add moment.js to your project if HotTowel does not. https://github.com/moment/moment
Moment recognizes the JSON you are describing.
A moment() is different than a JavaScript date, but it is easier to manipulate and parse.
This code you the current browser date from moment.
var now = window.moment().toDate();
This code demonstrates how to turn an ISO into a JavaScript Date object through moment.
// ISO 8601 datetime returned in JSON.
// In your code, you would pull it out of your the
// return variable in your dataservice.js
var DataInizio = "2012-12-10T22.00.00.000"
// convert your variable to a moment so you can parse it
var momentdatainizio = window.moment(DataInizio);
// convert the ISO to a javascript Date object so you can use it in js.
var mydate = window.moment(DataInizio).toDate();
Your Stada will end up in the breeze Metadata store which you use to populate your viewModel.
Retrieve the strada from the Metadata store or the database with something like this code in your dataservice.js. I am being a little more verbose than necessary so you can debug.
var getStrada = function (stradaId, callback) {
var query = EntityQuery.from("Strada")
.using(manager);
var pred = new breeze.Predicate("idd", "eq", stradaId);
// create the query
var queryb = query.where(pred);
// check the MetadataStore to see if you already have it
var localsession = queryb.executeLocally();
if (localsession) {
if (localsession.length > {
window.app.vm.strada.strada(data.results);
return localsession;
}
}
// get it from the server
else {
// return the promise to prevent blocking
// then set your viewModel when the query fulfills
// then make your callback if there is one
// handle the fail in your queryFailed function if there is a problem
return manager.executeQuery(queryb)
.then(function (data) {
window.app.vm.strada.strada(data.results);
})
.then(function () {
if ((typeof callback !== 'undefined' && callback !== null)) {
callback();
}
})
.fail(function () {
queryFailed();
});
}
};
Here is a fragment of a ko viewModel in strada.js
app.vm.strada = (function ($, ko, dataservice, router) {
var strada = ko.observable();
...
return {
strada : strada,
...
})($, ko, app.dataservice, app.router);
Here is the custom binding handler for knockout in the ko.bindingHandlers.js. This code is slightly verbose so you can debug the intermediate variables.
window.ko.bindingHandlers.DataInizio = {
// viewModel is a Strada
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var value = valueAccessor(), allBindings = allBindingsAccessor();
var valueUnwrapped = window.ko.utils.unwrapObservable(value);
var $el = $(element);
if (valueUnwrapped.toString().indexOf('Jan 1') >= 0)
$el.text("Strada not Started");
else {
var date = new Date(valueUnwrapped);
var d = moment(date);
$el.text(d.format('MM/DD/YYYY'));
}
}
};
Here is the html for the binding handler
...
Strada DataInizio:
...
I wrote this code based upon my code using Breeze v0.72 which uses sammy.js as the router. Your mileage may vary with newer versions of breeze and Durandel.
Related
I have the following setup:
The document:
[BsonCollection("Users")] // I get the collection name with a custom extension
[BsonIgnoreExtraElements]
public class UserDocument
{
[BsonId]
public Guid Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public UserSettingsModel UserSettings { get; set; }
}
public class UserSettingsModel
{
// ...
}
The repository:
public class UserRepository
{
private readonly IMongoCollection<UserDocument> _collection;
private readonly ILogger<UserRepository> _logger;
public UserRepository(IMongoDatabase database, ILogger<UserRepository> logger)
{
// returns "Users"
var collectionName = typeof(UserDocument).GetCollectionName();
_collection = database.GetCollection<UserDocument>(collectionName);
}
// ...
public async Task<UserDocument> GetById(Guid id)
{
var filter = Builders<UserDocument>.Filter.Eq(x => x.Id, id);
var user = await _collection.FindAsync(filter);
// var user = await _collection.FindAsync(x => x.Id == id); - doesn't work either
var request = filter.Render(
_collection.DocumentSerializer,
_collection.Settings.SerializerRegistry).ToString();
_logger.LogDebug(request);
return user.FirstOrDefault();
}
}
And I initialize the client this way:
// ...
BsonSerializer.RegisterSerializer(new GuidSerializer(GuidRepresentation.Standard));
var client = new MongoClient(connectionString);
var database = client.GetDatabase(dbName);
services.AddSingleton(c => database);
// convention pack and registries
// ...
// if moved here doesn't work either
// BsonSerializer.RegisterSerializer(new GuidSerializer(GuidRepresentation.Standard));
The filter generated in GetById is still the following: { "_id" : CSUUID("459f165a-4a91-4f39-906c-dc7401ee2468") } when I expect it to be UUID instead of CSUUID.
So, the query doesn't find anything and returns null. In the database the document I'm searching for has _id: UUID('459f165a-4a91-4f39-906c-dc7401ee2468')
What am I doing wrong?
I was able to fixing by this trick:
var mongoConnectionUrl = new MongoUrl(connectionString);
var mongoClientSettings = MongoClientSettings.FromUrl(mongoConnectionUrl);
// before initializing client
mongoClientSettings.GuidRepresentation = GuidRepresentation.Standard;
However setting the GuidRepresentation in client settings is obsolete, which is quite confusing. Also, the query generated still has CSUUID instead of UUID. I was able to log the query the following way:
mongoClientSettings.ClusterConfigurator = cb =>
{
cb.Subscribe<CommandStartedEvent>(e => logger.LogDebug($"{e.CommandName} - {e.Command.ToJson()}"));
};
If anyone finds a better way and post it here it would be appreciated.
I have a Kalem Entity with a collection of DigerKalemMaliyetleri property, which is a collection of MaliyetBirimi objects. DigerKalemMaliyetleri is of JSON type and stored at the same table as a JSON column.
public class Kalem
{
public int Id { get; set; }
[Column(TypeName = "json")]
public ICollection<MaliyetBirimi> DigerKalemMaliyetleri { get; set; }
}
public class MaliyetBirimi
{
public int? DovizCinsi { get; set; }
public decimal? Maliyet { get; set; }
}
When I try to update entity with only DigerKalemMaliyetleri property changed:
DataContext.Entry<Kalem>(first).CurrentValues.SetValues(second);
SQL Update command isn't executed and database record is not updated.
How could I update the entity without explicitly setting DigerKalemMaliyetleri property?
Regards
I had the same problem, you cann't actually use SetValues to update navigation property, you nead instead use DataContext.Update(YourNewObj) and then DataContext.SaveChanges();, or if you want to use SetValues approach, you need:
-Get the exist entry
Kalem existObj = DataContext.Kalems.Find(YourNewObj.Id);
-Loop in navigations of updating entry and the existing one to set the values of updating entry:
foreach(var navObj in DataContext.Entry(YourNewObj).Navigations)
{
foreach(var navExist in DatatContext.Entry(existObj).Navigations)
{
if(navObj.Metadata.Name == navExist.MetaData.Name)
navExist.CurrentValue = navObj.CurrentValue;
}
}
-Update also changes of direct properties:
DataContext.Entry(existObj).CurrentValues.SetValues(YourNewObj);
-Save your Updating:
DataContext.SaveChanges();
You can also check if you need to load your Navigations before going in foreach loop, otherwise you will get an error.
Please if you see beter scenario, correct me.
It's hard to know exactly what you're doing without a complete code sample. Note also that you're trying to set all properties of first from second, including e.g. the Id, which is probably not what you want.
Here's a complete code sample which works for me:
await using (var ctx = new BlogContext())
{
await ctx.Database.EnsureDeletedAsync();
await ctx.Database.EnsureCreatedAsync();
ctx.Kalem.Add(new()
{
DigerKalemMaliyetleri = new List<MaliyetBirimi>()
{
new() { DovizCinsi = 1, Maliyet = 2 }
}
});
await ctx.SaveChangesAsync();
}
await using (var ctx = new BlogContext())
{
var first = ctx.Kalem.Find(1);
var second = new Kalem
{
DigerKalemMaliyetleri = new List<MaliyetBirimi>()
{
new() { DovizCinsi = 3, Maliyet = 4 }
}
};
ctx.Entry(first).Property(k => k.DigerKalemMaliyetleri).CurrentValue = second.DigerKalemMaliyetleri;
await ctx.SaveChangesAsync();
}
public class BlogContext : DbContext
{
public DbSet<Kalem> Kalem { get; set; }
static ILoggerFactory ContextLoggerFactory
=> LoggerFactory.Create(b => b.AddConsole().AddFilter("", LogLevel.Information));
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseNpgsql(#"Host=localhost;Username=test;Password=test")
.EnableSensitiveDataLogging()
.UseLoggerFactory(ContextLoggerFactory);
}
public class Kalem
{
public int Id { get; set; }
[Column(TypeName = "json")]
public ICollection<MaliyetBirimi> DigerKalemMaliyetleri { get; set; }
}
public class MaliyetBirimi
{
public int? DovizCinsi { get; set; }
public decimal? Maliyet { get; set; }
}
I'm trying to get the Audit:NET EntityFramework.Core extension to write an AuditLog entry per changed property.
For this purpose I've overidden the EntityFrameworkDataProvider.InsertEvent with a custom DataProvider.
The problem is, using DbContextHelper.Core.CreateAuditEvent to create a new EntityFrameworkEvent returns null.
The reason seems to be, at this point in the code execution DbContextHelper.GetModifiedEntries determines all EF Entries have State.Unmodified, even if they are clearly included in the EventEntry changes.
I'm trying to circumvent CreateAuditEvent by manually creating the contents is impossible due to private/internal properties.
Maybe there is an alternative solution to this problem I'm not seeing, i'm open to all suggestions.
Audit entity class
public class AuditLog
{
public int Id { get; set; }
public string Description { get; set; }
public string OldValue { get; set; }
public string NewValue { get; set; }
public string PropertyName { get; set; }
public DateTime AuditDateTime { get; set; }
public Guid? AuditIssuerUserId { get; set; }
public string AuditAction { get; set; }
public string TableName { get; set; }
public int TablePK { get; set; }
}
Startup configuration
Audit.Core.Configuration.Setup()
.UseCustomProvider(new CustomEntityFrameworkDataProvider(x => x
.AuditEntityAction<AuditLog>((ev, ent, auditEntity) =>
{
auditEntity.AuditDateTime = DateTime.Now;
auditEntity.AuditAction = ent.Action;
foreach(var change in ent.Changes)
{
auditEntity.OldValue = change.OriginalValue.ToString();
auditEntity.NewValue = change.NewValue.ToString();
auditEntity.PropertyName = change.ColumnName;
}
}
Custom data provider class
public class CustomEntityFrameworkDataProvider : EntityFrameworkDataProvider
{
public override object InsertEvent(AuditEvent auditEvent)
{
var auditEventEf = auditEvent as AuditEventEntityFramework;
if (auditEventEf == null)
return null;
object result = null;
foreach (var entry in auditEventEf.EntityFrameworkEvent.Entries)
{
if (entry.Changes == null || entry.Changes.Count == 0)
continue;
foreach (var change in entry.Changes)
{
var contextHelper = new DbContextHelper();
var newEfEvent = contextHelper.CreateAuditEvent((IAuditDbContext)auditEventEf.EntityFrameworkEvent.GetDbContext());
if (newEfEvent == null)
continue;
newEfEvent.Entries = new List<EventEntry>() { entry };
entry.Changes = new List<EventEntryChange> { change };
auditEventEf.EntityFrameworkEvent = newEfEvent;
result = base.InsertEvent(auditEvent);
}
}
return result;
}
}
Check my answer here https://github.com/thepirat000/Audit.NET/issues/323#issuecomment-673007204
You don't need to call CreateAuditEvent() you should be able to iterate over the Changes list on the original event and call base.InsertEvent() for each change, like this:
public override object InsertEvent(AuditEvent auditEvent)
{
var auditEventEf = auditEvent as AuditEventEntityFramework;
if (auditEventEf == null)
return null;
object result = null;
foreach (var entry in auditEventEf.EntityFrameworkEvent.Entries)
{
if (entry.Changes == null || entry.Changes.Count == 0)
continue;
// Call base.InsertEvent for each change
var originalChanges = entry.Changes;
foreach (var change in originalChanges)
{
entry.Changes = new List<EventEntryChange>() { change };
result = base.InsertEvent(auditEvent);
}
entry.Changes = originalChanges;
}
return result;
}
Notes:
This could impact performance, since it will trigger an insert to the database for each column change.
If you plan to use async calls to DbContext.SaveChangesAsync, you should also implement the InsertEventAsync method on your CustomDataProvider
The Changes property is only available for Updates, so if you also want to audit Inserts and Deletes, you'll need to add the logic to get the column values from the ColumnValues property on the event
I am trying to get the value of a "NotMapped" property for a Entity/class when intercepting a DbUpdateCommandTree.
I have looked through the various metadata, but I cannot find the "link" to the Entity from the CommandTree, so unfortunately I am stuck.
Is it even possible ?
public class SomeEntity
{
public int ID { get; set; }
[NotMapped]
public int SomeUnmappedProperty { get; set; }
}
public class CommandTreeInterceptor : IDbCommandTreeInterceptor
{
public void TreeCreated(DbCommandTreeInterceptionContext ctx)
{
if (ctx.OriginalResult.DataSpace == DataSpace.SSpace)
{
var updateCommand = ctx.OriginalResult as DbUpdateCommandTree;
if (updateCommand != null)
{
// I would like to get a value of a specific property here.
// Pseudo code
var val = updateCommand.Entity.GetPropertyValue("SomeUnmappedProperty") as int;
}
}
}
}
I'm trying to use Simon Ince's conditional validation attributes for one of my view models. The logic seems to be working spot on, but the attribute's error message isn't appearing in my view's ValidationFor() methods.
The attribute:
public class RequiredIfAttribute : ValidationAttribute
{
private RequiredAttribute innerAttribute = new RequiredAttribute();
public string DependentProperty { get; set; }
public object TargetValue { get; set; }
public RequiredIfAttribute(string dependentProperty, object targetValue)
{
this.DependentProperty = dependentProperty;
this.TargetValue = targetValue;
}
public override bool IsValid(object value)
{
return innerAttribute.IsValid(value);
}
}
The validator:
public class RequiredIfValidator : DataAnnotationsModelValidator<RequiredIfAttribute>
{
public RequiredIfValidator(ModelMetadata metadata, ControllerContext context, RequiredIfAttribute attribute)
: base(metadata, context, attribute)
{
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
// no client validation - I might well blog about this soon!
return base.GetClientValidationRules();
}
public override IEnumerable<ModelValidationResult> Validate(object container)
{
// get a reference to the property this validation depends upon
var field = Metadata.ContainerType.GetProperty(Attribute.DependentProperty);
if (field != null)
{
// get the value of the dependent property
var value = field.GetValue(container, null);
// compare the value against the target value
if ((value == null && Attribute.TargetValue == null) ||
(value.Equals(Attribute.TargetValue)))
{
// match => means we should try validating this field
if (!Attribute.IsValid(Metadata.Model))
// validation failed - return an error
yield return new ModelValidationResult { Message = ErrorMessage };
}
}
}
}
How they're hooked up (Global.asax.cs):
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute), typeof(RequiredIfValidator));
}
}
And how I'm trying to use it:
public class AdminGameViewModel
{
public bool IsCreated { get; set; }
[Required]
public int GameID { get; set; }
[Required(ErrorMessage = "A game must have a title")]
[DisplayFormat(ConvertEmptyStringToNull=false)]
public string GameTitle { get; set; }
[Required(ErrorMessage = "A short URL must be supplied")]
[DisplayFormat(ConvertEmptyStringToNull=false)]
public string Slug { get; set; }
[RequiredIf("IsCreated", true, ErrorMessage = "A box art image must be supplied")]
public HttpPostedFileBase BoxArt { get; set; }
[RequiredIf("IsCreated", true, ErrorMessage = "A large image for the index page is required")]
public HttpPostedFileBase IndexImage { get; set; }
// other props of the class....
}
I don't know enough about the inner workings of MVC's validation mechanism in order to troubleshoot my problem. Any ideas?
Have you tried updating to my MVC3 implementation? It is cleaner than the hack needed with the validator in MVC2.
One thing that is missing from even the MVC3 code is the need to override FormatErrorMessage on the attribute, which will likely be similar to what you're seeing here. For the MVC 3 code I use;
public override string FormatErrorMessage(string name)
{
if (!String.IsNullOrEmpty(this.ErrorMessage))
innerAttribute.ErrorMessage = this.ErrorMessage;
return innerAttribute.FormatErrorMessage(name);
}
HTH
Simon