I use the following pattern when importing "unknown" data.
public class MyCustomObject
{
public string MyCustomDateAsString { get; set; }
public DateTime? MyCustomDate
{
get
{
DateTime? returnValue = null;
DateTime parseResult = DateTime.MinValue;
bool parseAttempt = DateTime.TryParse(this.MyCustomDateAsString, out parseResult);
if (parseAttempt)
{
returnValue = parseResult;
}
return returnValue;
}
}
public string MyCustomIntAsString { get; set; }
public int? MyCustomInt
{
get
{
int? returnValue = null;
int parseResult = 0;
bool parseAttempt = int.TryParse(this.MyCustomIntAsString, out parseResult);
if (parseAttempt)
{
returnValue = parseResult;
}
return returnValue;
}
}
}
I have this working.
public class MyCustomObjectValidator : AbstractValidator<MyCustomObject>
{
public MyCustomObjectValidator()
{
RuleFor(custobj => custobj.MyCustomDateAsString).NotEmpty().WithMessage("Please specify a MyCustomDateAsString");
RuleFor(custobj => custobj.MyCustomIntAsString).NotEmpty().WithMessage("Please specify a MyCustomIntAsString");
}
}
I want to add these rules.
RuleFor(custobj => custobj.MyCustomDate).NotNull().WithMessage("MyCustomDate must be a valid non null date. You specified '{0}'"); /* How can I put MyCustomDateAsString for the {0} */
RuleFor(custobj => custobj.MyCustomInt).NotNull().WithMessage("MyCustomInt must be a valid non null int. You specified '{0}'"); /* How can I put MyCustomIntAsString for the {0} */
But I don't know how to get the MyCustomDateAsString and MyCustomIntAsString to show up in the error messages for MyCustomDate and MyCustomInt.
So with most validators you'd use the {PropertyValue} placeholder to get the current property value but this doesn't help in this case as you want the value of a different property.
However, There's an overload of WithMessage that takes a func that you can use to build custom placehold values.
RuleFor(x=>x.Foo).NotNull().WithMessage("...blah {0}", x=>x.SomeOtherProperty);
The final argument is actually a params array of Func[T,object] so you can specify as many of these as you like, and they're processed in order.
Related
I have a Model with a property of a value object type as following:
public class Course : AggregateRoot, ISpModel
{
...
public UnsignedNumber MaximumCapacity { get; private set; }
...
}
with UnsignedNumber being a value object containing a short value:
public class UnsignedNumber : BaseValueObject<UnsignedNumber>
{
public short Value { get; }
...
}
What I need to do is to sum all the MaximumCapacities of courses which correspond with certain conditions, but when I try to add a SumAsync(x => x.MaximumCapacity) at the end of the query, I get a syntax error
the syntax error
and when I try to do the same with it's value, I get a linq error in runtime.
"The LINQ expression '(int)(EntityShaperExpression: \r\n EntityType: Course\r\n ValueBufferExpression: \r\n (ProjectionBindingExpression: Outer)\r\n IsNullable: False\r\n).MaximumCapacity.Value' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information."
Edit:
Here's the Linq Expression that encounters the problem:
var query = _dbContext.Courses.AsQueryable();
query = query.Include(x => BunchOfIncludes(x));
var res = await query.Where(x => BunchOfClauses(x)).SumAsync(x => x.MaximumCapacity.Value);
Edit2: the classes mentioned above:
public abstract class AggregateRoot : Entity
{
private readonly List<IDomainEvent> _events;
protected AggregateRoot() => _events = new List<IDomainEvent>();
public AggregateRoot(IEnumerable<IDomainEvent> events)
{
if (events == null) return;
foreach (var #event in events)
((dynamic)this).On((dynamic)#event);
}
protected void AddEvent(IDomainEvent #event) => _events.Add(#event);
public IEnumerable<IDomainEvent> GetEvents() => _events.AsEnumerable();
public void ClearEvents() => _events.Clear();
}
public interface ISpModel
{
}
public abstract class BaseValueObject<TValueObject> : IEquatable<TValueObject>
where TValueObject : BaseValueObject<TValueObject>
{
...
public static bool operator ==(BaseValueObject<TValueObject> right, BaseValueObject<TValueObject> left)
{
if (right is null && left is null)
return true;
if (right is null || left is null)
return false;
return right.Equals(left);
}
...
}
below code(search function) works fine.
public class BookItem
{
public string Title { get; set; }
public string OriginalTitle { get; set; }
}
public IEnumerable<dynamic> Search(string keyword)
{
/*MATCH (n:`Book`) RETURN n*/
var query = client
.Cypher
.Match("(n:Book)")
.Return(n => n.As<BookItem>());
return query.Results;
}
However, i don't want to declare a class like BookItem. I just want all results in a dynamic object. Is there a way to do that?
For example below code runs and returns empty object, it doesn't return any attributes..
public IEnumerable<dynamic> Search(string keyword)
{
/*MATCH (n:`Book`) RETURN n*/
var query = client
.Cypher
.Match("(n:Book)")
.Return(n => n.As<dynamic>());
return query.Results;
}
The basic gist is in the answer to this question: Casting nodes of an unknown type
What you end up returning is Node<string> and parsing using Json.net into a dynamic object, there is no direct way of just doing x.As<dynamic>() unfortunately.
Working on creating my first Orchard Module and I am running into issues getting the form data saved back to the database. I have everything registered correctly as far as I can tell from looking at a lot of samples so I must be missing something minor.
I am able to get the Apartment form to show under the new menu, validation is working but when I fill the form completly and hit save I get:
Your Apartment has been created.
Checking the database the record is not in the table and checking the logs shows:
2013-12-19 09:15:23,416 [19]
NHibernate.Transaction.ITransactionFactory - DTC transaction prepre
phase failed NHibernate.Exceptions.GenericADOException: could not
execute batch command.[SQL: SQL not available] --->
System.Data.SqlClient.SqlException: Cannot insert the value NULL into
column 'FloorPlanName', table
'Orchard.dbo.CommunityWebsiteSolutions_ApartmentPartRecord';
column does not allow nulls. INSERT fails.
Running SQL Profiler shows an insert with all columns being set to NULL.
Migrations.cs
SchemaBuilder.CreateTable(typeof(ApartmentPartRecord).Name, table => table
.ContentPartRecord()
.Column<string>("FloorPlanName", c => c.WithLength(25).NotNull())
.Column<string>("FullAddress", c => c.WithLength(256).NotNull()))
.Column<string>("ShortDescription", c => c.WithLength(150).NotNull())
.Column("NumberOfBedrooms", DbType.Int32, c => c.NotNull())
.Column("NumberOfBathrooms", DbType.Int32, c => c.NotNull())
.Column("SquareFootage", DbType.Int32, c => c.NotNull())
.Column("WhenAvailable", DbType.DateTime)
.Column("RentAmount", DbType.Decimal)
);
ContentDefinitionManager.AlterPartDefinition(typeof (ApartmentPart).Name, part => part.Attachable());
ApartmentPart
public class ApartmentPartRecord : ContentPartRecord {
public virtual string FloorPlanName { get; set; }
public virtual string ShortDescription { get; set; }
public virtual string FullAddress { get; set; }
public virtual int? NumberOfBedrooms { get; set; }
public virtual int? NumberOfBathrooms { get; set; }
public virtual int? SquareFootage { get; set; }
public virtual DateTime? WhenAvailable { get; set; }
public virtual decimal? RentAmount { get; set; }
}
public class ApartmentPart : ContentPart<ApartmentPartRecord> {
[Required, StringLength(256)]
[Display(Name = "Address / Unit Number")]
public string FullAddress {
get { return Record.FullAddress; }
set { Record.FullAddress = value; }
}
[Required, StringLength(25)]
[Display(Name = "Floor Plan")]
public string FloorPlanName {
get { return Record.FloorPlanName; }
set { Record.FloorPlanName = value; }
}
[Required, StringLength(150)]
[Display(Name = "Sales Description")]
public string ShortDescription {
get { return Record.ShortDescription; }
set { Record.ShortDescription = value; }
}
[Required]
[Display(Name = "Bedroom Count")]
public int? NumberOfBedrooms {
get { return Record.NumberOfBedrooms; }
set { Record.NumberOfBedrooms = value; }
}
[Required]
[Display(Name = "Bathroom Count")]
public int? NumberOfBathrooms {
get { return Record.NumberOfBathrooms; }
set { Record.NumberOfBathrooms = value; }
}
[Required]
[Display(Name = "Square Footage")]
public int? SquareFootage {
get { return Record.SquareFootage; }
set { Record.SquareFootage = value; }
}
[Display(Name = "First Availability")]
public DateTime? WhenAvailable {
get { return Record.WhenAvailable; }
set { Record.WhenAvailable = value; }
}
[Display(Name = "Rent Amount")]
public decimal? RentAmount {
get { return Record.RentAmount; }
set { Record.RentAmount = value; }
}
}
Driver
public class ApartmentPartDriver : ContentPartDriver<ApartmentPart>
{
protected override string Prefix
{
get { return "Apartment"; }
}
//GET
protected override DriverResult Editor(ApartmentPart part, dynamic shapeHelper)
{
return ContentShape("Parts_Apartment_Edit",
() => shapeHelper.EditorTemplate(
TemplateName: "Parts/Apartment",
Model: part,
Prefix: Prefix));
}
//POST
protected override DriverResult Editor(ApartmentPart part, IUpdateModel updater, dynamic shapeHelper)
{
updater.TryUpdateModel(part, Prefix, null, null);
return Editor(part, shapeHelper);
}
}
Handler
public class ApartmentPartHandler : ContentHandler {
public ApartmentPartHandler(IRepository<ApartmentPartRecord> repository)
{
Filters.Add(StorageFilter.For(repository));
}
}
Your error message explains this pretty clearly:
System.Data.SqlClient.SqlException: Cannot insert the value NULL into column 'FloorPlanName', table 'Orchard.dbo.CommunityWebsiteSolutions_ApartmentPartRecord'; column does not allow nulls. INSERT fails.
Your problem occurs because:
You are using nullable types such as string and int? types in your Record class, which means you want to allow nulls.
Yet, you are specifying in your DB migration that you want to disallow nulls.
And when C# instantiates your Record class, it initializes the fields using the default value, which is null for nullable types.
You can do one of the following:
Make your DB columns nullable (remove NotNull)
Make your Record class use non-nullable types (for example, int instead of int?). Note that this is not an option for reference types such as string.
Give non-null default values to the fields of your Record class by giving the class a constructor. This is arguably bad practice since you will be calling virtual properties in a base class, but seems to be ok in NHibernate.
Give non-null default values to the fields of your Record class by giving your part an OnInitializing handler, which would be placed in your Handler class.
UPDATE
You commented that you are expecting the fields to be filled in by the TryUpdateModel in the Editor function of your driver class. This does eventually happen, but the actual sequence of events that occurs is this (you can see this in the CreatePOST method of Orchard.Core.Contents.Controllers.AdminController):
ContentManager.New() with the content type ID to create content item in memory. This step calls OnInitializing for the appropriate content parts for the content type, which are defined in handlers.
ContentManager.Create() with the content item in Draft Mode. This step actually tries to persist the item to the DB once.
ContentManager.UpdateEditor(). This is the call that actually calls Editor of the appropriate driver for the content type.
Check the ModelState and roll back the transaction if anything has failed.
Step 2 will fail if you have NULL values in columns marked NotNull, because the fields have default values at that point. For these columns, you have to fill them in before step 2 by using OnInitializing or by using a constructor on your Record part.
In other words, TryUpdateModel in your driver is actually applying changes directly to the entity that has already been Created and is now attached to the NHibernate session.
I'm working on an MVC 4 project and trying to convert a value in a KeyValue list to a nullable DateTime. I have used the following line in the mapper (I've not included the other properties as there are a lot)
.ForMember(d => d.Deadline, m => m.ResolveUsing<DeadlineResolver>())
My resolver looks like this:
public class DeadlineResolver : ValueResolver<Booking, DateTime?>
{
protected override DateTime? ResolveCore(Booking source, ResolutionResult resolutionResult)
{
KeyValue keyValue = source.KeyValues.FirstOrDefault(k => k.Key.KeyId == "DEADLINE");
return (keyValue != null) ? DateTime.Parse(keyValue.Value) : (DateTime?)null;
}
}
The value of deadline which is defined as shown below is never returned as null but DateTime.MinDate instead. I need it to be null when I'm the binding the result in a view so that I only show a value when there is a date.
public DateTime? Deadline { get; set; }
How do I make these values null without going over the values after mapping to look for min dates and set to null (temp hack I've put in place so the code runs)?
Using LinqPad and AutoMapper 2.2.1 the following gives me a valid date when KeyValue has a date, and a null DateTime when KeyValue is null. (Note there are minor changes to the resolver to simplify it as the class definitions weren't provided).
void Main()
{
AutoMapper.Mapper.CreateMap<Booking, dest>()
.ForMember(d => d.Deadline, m => m.ResolveUsing<DeadlineResolver>());
AutoMapper.Mapper.AssertConfigurationIsValid();
// Gives a valid DateTime
var booking = new Booking { KeyValue = "2013-01-01" };
booking.Dump();
var rc = AutoMapper.Mapper.Map<Booking, dest>(booking);
rc.Dump();
// Gives a null DateTime
booking = new Booking { KeyValue = null };
booking.Dump();
rc = AutoMapper.Mapper.Map<Booking, dest>(booking);
rc.Dump();
}
// Define other methods and classes here
public class Booking
{
public string KeyValue { get; set; }
}
public class dest
{
public DateTime? Deadline { get; set; }
}
public class DeadlineResolver : AutoMapper.ValueResolver<Booking, DateTime?>
{
protected override DateTime? ResolveCore(Booking source)
{
return (source.KeyValue != null)
? DateTime.Parse(source.KeyValue)
: (DateTime?)null;
}
}
Is this the functionality you were after? If so, then the issue could be with an older version of AutoMapper, or an unexpected KeyValue value.
One day I decided to build this nice multi-tier application using L2S and WCF.
The simplified model is : DataBase->L2S->Wrapper(DTO)->Client Application.
The communication between Client and Database is achieved by using Data Transfer Objects which contain entity objects as their properties.
abstract public class BaseObject
{
public virtual IccSystem.iccObjectTypes ObjectICC_Type
{
get { return IccSystem.iccObjectTypes.unknownType; }
}
[global::System.Data.Linq.Mapping.ColumnAttribute(Storage = "_ID", AutoSync = AutoSync.OnInsert, DbType = "BigInt NOT NULL IDENTITY", IsPrimaryKey = true, IsDbGenerated = true)]
[global::System.Runtime.Serialization.DataMemberAttribute(Order = 1)]
public virtual long ID
{
//get;
//set;
get
{
return _ID;
}
set
{
_ID = value;
}
}
}
[DataContract]
public class BaseObjectWrapper<T> where T : BaseObject
{
#region Fields
private T _DBObject;
#endregion
#region Properties
[DataMember]
public T Entity
{
get { return _DBObject; }
set { _DBObject = value; }
}
#endregion
}
Pretty simple, isn't it?. Here's the catch. Each one of the mapped classes contains ID property itself so I decided to override it like this
[global::System.Data.Linq.Mapping.TableAttribute(Name="dbo.Divisions")]
[global::System.Runtime.Serialization.DataContractAttribute()]
public partial class Division : INotifyPropertyChanging, INotifyPropertyChanged
{
[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_ID", AutoSync=AutoSync.OnInsert, DbType="BigInt NOT NULL IDENTITY", IsPrimaryKey=true, IsDbGenerated=true)]
[global::System.Runtime.Serialization.DataMemberAttribute(Order=1)]
public override long ID
{
get
{
return this._ID;
}
set
{
if ((this._ID != value))
{
this.OnIDChanging(value);
this.SendPropertyChanging();
this._ID = value;
this.SendPropertyChanged("ID");
this.OnIDChanged();
}
}
}
}
Wrapper for division is pretty straightforward as well:
public class DivisionWrapper : BaseObjectWrapper<Division>
{
}
It worked pretty well as long as I kept ID values at mapped class and its BaseObject class the same(that's not very good approach, I know, but still) but then this happened:
private CentralDC _dc;
public bool UpdateDivision(ref DivisionWrapper division)
{
DivisionWrapper tempWrapper = division;
if (division.Entity == null)
{
return false;
}
try
{
Table<Division> table = _dc.Divisions;
var q = table.Where(o => o.ID == tempWrapper.Entity.ID);
if (q.Count() == 0)
{
division.Entity._errorMessage = "Unable to locate entity with id " + division.Entity.ID.ToString();
return false;
}
var realEntity = q.First();
realEntity = division.Entity;
_dc.SubmitChanges();
return true;
}
catch (Exception ex)
{
division.Entity._errorMessage = ex.Message;
return false;
}
}
When trying to enumerate over the in-memory query the following exception occurred:
Class member BaseObject.ID is unmapped.
Although I'm stating the type and overriding the ID property L2S fails to work.
Any suggestions?
Suppose I found the problem.
When writing
var q = table.Where(o => o.ID == tempWrapper.Entity.ID);
the compiler implies that the object is of BaseObject type and therefore tries to get its ID value from the BaseObject mapping and it's unmapped.
The problem seems to be resolved by explicitly declaring the type:
var q = from Division div in _dc.GetTable<Division>()
where div.ID == tempWrapper.Entity.ID
select div;