I'm getting the following error:
"deleted object would be re-saved by cascade (remove deleted object from associations)"
I have trimmed the entirety of the ajax call to the following:
[HttpPost]
[UnitOfWork(Scope = FilterScope.Result)]
public ActionResult SaveEditMode(long id, AddTrackedRowViewModel model, string editMode, List<string> elementNames, string provisionData)
{
var cell = _supplementCoordinator.GetSupplement(id).TrackedTables.First(x => x.Name == model.Name).TrackedRows.First(x => x.Ordinal == model.Ordinal).TrackedCells.First(x => x.Name == "Detail");
_supplementCoordinator.RemoveChildren(cell);
return Json( new {Success = true});
}
public bool RemoveChildren(TrackedNode parentNode)
{
foreach (TrackedField trackedField in parentNode.ChildNodes)
{
_trackedFieldRepository.Delete(trackedField);
}
return true;
}
My mappings are as follows
mapping.HasMany(x => x.ChildNodes).KeyColumn("ParentNodeId").Inverse();
mapping.References(x => x.ParentNode);
Just remove the child nodes from the parent collection just as the error suggests:
public bool RemoveChildren(TrackedNode parentNode)
{
foreach (TrackedField trackedField in new List<TrackField>(parentNode.ChildNodes))
{
_trackedFieldRepository.Delete(trackedField);
_parentNode.Remove(trackField);
}
return true;
}
Related
I have the following code :
RuleFor(record => record)
.Must(WholeObject => Mandatory(WholeObject, keyValuePairs))
.WithName( X => X.KeyName).WithMessage("KeyValue is mandatory but some values are missing")
//Here X.KeyValue contains the value.
//I want to pass this value on error
private bool Mandatory(Object recObj, Object keyValuePairs)
{
//return true or false depeneds on the logic
}
How do I pass the X.KeyValue in WithMessage?, If there is an error it returns .WithMessage("KeyValue is mandatory but some values are missing") but how do I pass the actual value ?
X contains X.KeyName and X.KeyValue
Note:
X.KeyValue is not a string.
public class KeyValue
{
public List<string> Val { get; set; }
}
RuleFor(record => record)
.Must(WholeObject => SizeOF(WholeObject, keyValuePairs))
.WithName(X => X.KeyName).WithMessage(x => $"{x.KeyValue.Val[0]} is not in range(min, max) as defined");
unfortunately this prints only the first value. Is it a way to include only the error value?
I used this
.WithName(X => X.KeyName).WithMessage(x => $"
{x.KeyValue.Val.ToList().ForEach(s => s)} is not in range(min, max) as
defined");
but this didnot work.
private bool SizeOF(Entity.EigenData.Record recObj, IDictionary<string, Entity.EigenSchema.AttributeSet> keyValuePairs)
{
string strval = recObj.KeyName;
Entity.EigenSchema.AttributeSet obj = keyValuePairs[recObj.KeyName];
//if the size falls inbetween min and max
return recObj.KeyValue.ISSizeWithinRange(obj);
//string val = obj.KeyValue.ToString();
}
public static bool ISSizeWithinRange(this Validation.Entity.EigenData.KeyValue kv, Validation.Entity.EigenSchema.AttributeSet Obj)
{
try
{
if (kv.Val.Count > 0) //only if List<val> is available go inside the loop
{
foreach (string s in kv.Val)
{
//foreach val check if its empty or null, if its empty or null then return false
bool True = String.IsNullOrEmpty(s);
if (True)
{
return true;
}
else
{
bool False = (Enumerable.Range(Obj.Size.Min, Obj.Size.Max).Contains(s.Length));
// if it contains within range then do nothing, return true at the end
//if it doesnot fall with in range then return false immediately. No need to check the entire set of values
if(!False)
{
return false;
}
}
}
//if it contains some value then return true
return true;
}
else
{
//List<val> count is zero
return false;
}
}
catch
{
return false;
}
}
Change your code like below:
RuleFor(record => record)
.Must(WholeObject => Mandatory(WholeObject,keyValuePairs))
.WithName(X => X.KeyName).WithMessage(x => $"{x.KeyName} is mandatory but some values are missing");
Whole code:
public class Status
{
public string Name { get; set; }
}
public class CustomValidator : AbstractValidator<Status>
{
public CustomValidator ()
{
RuleFor(record => record)
.Must(WholeObject => Mandatory(WholeObject,keyValuePairs))
.WithName(X => X.Name).WithMessage(x => $"{x.Name} is mandatory but some values are missing");
}
private bool Mandatory(Object recObj, Object keyValuePairs)
{
//return true or false depeneds on the logic
return false;
}
}
I am querying a list of objects, and then correlating them with a subquery.
I want to return the results of the subquery, as well as the root entity. But
I can't figure out how to actually return the root entity, I can only return individual properties of it.
Specifically, this works:
this.Session.QueryOver<MediaFile>(() => mediaFile)
.SelectList(list => list.Select(mf => mf.Id));
But this does not:
this.Session.QueryOver<MediaFile>(() => mediaFile)
.SelectList(list => list.Select(mf => mf));
I get the following error:
Could not resolve property : of MediaFile
Does anyone know how I can include the entity as a property in the list?
Here's my complete example:
// My target class
private class MediaFileAndCount
{
// I can successfully populate these fields.
public long MediaFileId { get; set; }
public int DistinctPlaylistCount { get;set; }
// But I want to populate this field!
public MediaFile MediaFile { get; set; }
}
private void TrySingleQuery()
{
MediaFile mediaFile = null;
PlaylistEntry playlistEntry = null;
MediaFileAndCount mfc = null;
var subQuery = QueryOver.Of<PlaylistEntry>(() => playlistEntry)
.Where(() => playlistEntry.MediaFile.Id == mediaFile.Id)
.Select(Projections.CountDistinct<PlaylistEntry>(p => p.Playlist));
var query = this.Session.QueryOver<MediaFile>(() => mediaFile)
.SelectList(list => list
.Select(mf => mf.Id).WithAlias(() => mfc.MediaFileId)
.Select(Projections.SubQuery(subQuery)).WithAlias(() => mfc.DistinctPlaylistCount)
// .Select(mf => mf).WithAlias(() => mfc.MediaFile) // This line fails
)
.TransformUsing(Transformers.AliasToBean<MediaFileAndCount>());
var results = query.List<MediaFileAndCount>();
}
another way to query it
var allMediaFiles = session.QueryOver<MediaFile>().Where(...).ToFuture(); // just get the MediaFiles into sessionCache
var results = session.QueryOver<PlaylistEntry>()
.Where(p => p.MediaFile...)
.SelectList(list => list
.GroupBy(p => p.MediaFile.Id)
.CountDistinct(p => p.Playlist))
.ToFuture<object[]>()
.Select(a => new MediaFileAndCount
{
MediaFile = session.Get<MediaFile>((long)a[0]),
DistinctPlaylistCount = (int)a[1]
})
.ToList();
foreach (var mediaFile in allMediaFiles.Except(results.Select(r => r.MediaFile)))
{
results.Add(new MediaFileAndCount { MediaFile = mediaFile });
}
I have defined this mapping:
public class Mapping : ConventionModelMapper
{
public Mapping()
{
IsRootEntity((type, declared) =>
{
return !type.IsAbstract &&
new[] { typeof(Entity<Guid>), typeof(CommonEntity) }.Contains(type.BaseType);
});
IsEntity((x, y) => typeof(Entity<Guid>).IsAssignableFrom(x) && !x.IsAbstract && !x.IsInterface);
IsSet((mi, wasDeclared) =>
{
var propertyType = mi.GetPropertyOrFieldType();
return propertyType.IsGenericType && typeof(System.Collections.Generic.ISet<>).IsAssignableFrom(propertyType.GetGenericTypeDefinition());
});
IsManyToMany((mi, wasDeclared) =>
{
var propertyType = mi.GetPropertyOrFieldType();
var containingType = mi.ReflectedType;
if (typeof(System.Collections.Generic.ISet<>).IsAssignableFrom(propertyType.GetGenericTypeDefinition()))
{
var referenceType = propertyType.GetGenericArguments()[0];
return true;
return !referenceType.GetProperties(BindingFlags.Instance | BindingFlags.Public).Any(p => p.PropertyType.IsAssignableFrom(containingType));
}
return false;
});
Class<Entity<Guid>>(x =>
{
x.Id(c => c.Id, m => m.Generator(Generators.GuidComb));
x.Version(c => c.Version, (vm) => { });
});
BeforeMapClass += OnBeforeMapClass;
BeforeMapManyToOne += OnBeforeMapManyToOne;
BeforeMapSet += OnBeforeMapSet;
BeforeMapManyToMany += OnBeforeMapManyToMany;
Class<CommonEntity>(x =>
{
x.Property(c => c.DateCreated, m => m.Type<UtcDateTimeType>());
x.Property(c => c.DateModified, m => m.Type<UtcDateTimeType>());
});
}
private void OnBeforeMapManyToMany(IModelInspector modelInspector, PropertyPath member, IManyToManyMapper collectionRelationManyToManyCustomizer)
{
collectionRelationManyToManyCustomizer.Column(member.LocalMember.GetPropertyOrFieldType().GetGenericArguments()[0].Name + "Id");
}
private void OnBeforeMapSet(IModelInspector modelInspector, PropertyPath member, ISetPropertiesMapper propertyCustomizer)
{
propertyCustomizer.Key(k=>k.Column(member.GetContainerEntity(modelInspector).Name + "Id"));
propertyCustomizer.Cascade(Cascade.Persist);
if (modelInspector.IsManyToMany(member.LocalMember))
{
propertyCustomizer.Table(member.GetContainerEntity(modelInspector).Name +
member.LocalMember.GetPropertyOrFieldType().GetGenericArguments()[0].Name);
}
}
private void OnBeforeMapManyToOne(IModelInspector modelInspector, PropertyPath member, IManyToOneMapper propertyCustomizer)
{
propertyCustomizer.Column(member.LocalMember.Name + "Id");
}
private void OnBeforeMapClass(IModelInspector modelInspector, Type type, IClassAttributesMapper classCustomizer)
{
classCustomizer.Table('['+ type.Name + ']');
}
}
An I am having a problem with a many to many relationship. I have User, UserPermission and Permission. When I am saving a user after attaching a Permission to it it generates this SQL:
exec sp_executesql N'UPDATE [Permission] SET UserId = #p0 WHERE Id = #p1',N'#p0 uniqueidentifier,#p1 uniqueidentifier',#p0='57A2CD87-4A79-4131-B9CE-A1060168D520',#p1='9D99D340-1B63-4291-B55A-6127A8F34FC9'
When it should be like:
exec sp_executesql N'INSERT INTO UserPermission (UserId, PermissionId) VALUES (#p0, #p1)',N'#p0 uniqueidentifier,#p1 uniqueidentifier',#p0='2C670A01-C2E6-46A3-A412-A1060168F976',#p1='9D99D340-1B63-4291-B55A-6127A8F34FC9'
When I add a specific class mapping for User:
Class<User>(classMapper =>
{
classMapper.Set(x => x.Permissions, map =>
{
//map.Key(k => k.Column("UserId"));
//map.Table("UserPermission");
}, r => r.ManyToMany(m => {}));
});
I can leave out the key and table definition and include the ManyToMany without the column call and it works. But it does the same same thing as my BeforeManyToMany event handler. If I drop the whole Class thing the BeforeMapManyToMany event is not fired and nHibernate thinks I've got a UserId on my Permission table.
Heres User:
public class User : CommonEntity
{
protected User()
{
Permissions = new HashSet<Permission>();
}
public User(User createdBy) : base(createdBy)
{
Permissions = new HashSet<Permission>();
}
public ISet<Permission> Permissions { get; protected set; }
}
After poking around in the source code I realised the problem was that IsOneToMany was checked against the property when defining the set before IsManyToMany. I just needed to define IsOneToMany and it worked without any explicit mappings.
IsOneToMany((mi, wasDeclared) =>
{
var propertyType = mi.GetPropertyOrFieldType();
return ModelInspector.IsEntity(propertyType);
});
above is code which I use to manipulate with data from my domain to dto model, which I use for wcf serialization. My question is how to pass object mother with collection of childrens into MotherDTO. With current code situation I pass only data without collection children. Do I need to use session in line and to add session MotherDTO dto = new MotherDTO(data, session); and to use that session to retreive collection of childrens in dto. If so, how ? Please help.
Regards,
public MotherDTO GetMotherData()
{
using (ISession session = instance.OpenSession())
{
using (ITransaction tx = session.BeginTransaction())
{
Mother data = session.Query<Mother>()
.Fetch(x => x.Childrens)
.FirstOrDefault();
tx.Commit();
MotherDTO dto = new MotherDTO(data);
return dto;
}
}
}
MotherDTO.cs
public MotherDTO(Mother x)
{
Name = x.Name;
List<Children>Childrens= new List<Children>();
foreach (Children obj in x.Childrens)
{
States.Add(obj);
}
}
Mother.cs
public virtual string Name
{
get { return _Name; }
set
{
_Name = value;
}
}
public virtual Iesi.Collections.Generic.ISet<Children> Childrens
{
get
{
return _Childrens;
}
set
{
if (_Childrens == value)
return;
_Childrens = value;
}
}
Since you're already (eager) loading your Children collection you can use Automapper to populate your DTOs.
If you want to know how to configure Automapper to work with nested collection you can read here:
Mapper.CreateMap<Order, OrderDto>()
.ForMember(dest => dest.OrderLineDtos, opt => opt.MapFrom(src => src.OrderLines));
Mapper.CreateMap<OrderLine, OrderLineDto>()
.ForMember(dest => dest.ParentOrderDto, opt => opt.MapFrom(src => src.ParentOrder));
Mapper.AssertConfigurationIsValid();
First let me apologize a bit for the length of this post, it's mostly code though so I hope you all bear with me!
I have a scenario in dealing with a legacy database, where I needed to write an IUserType using NHibernate 3.2 to take a 2 character "status" field and return a Boolean value from it. The status field can hold 3 possible values:
* 'DI' // 'Disabled', return false
* ' ' // blank or NULL, return true
* NULL
Here is what I have simplified.
Table Definition:
CREATE TABLE [dbo].[Client](
[clnID] [int] IDENTITY(1,1) NOT NULL,
[clnStatus] [char](2) NULL,
[clnComment] [varchar](250) NULL,
[clnDescription] [varchar](150) NULL,
[Version] [int] NOT NULL
)
Fluent Mapping:
public class ClientMapping : CoreEntityMapping<Client>
{
public ClientMapping()
{
SchemaAction.All().Table("Client");
LazyLoad();
Id(x => x.Id, "clnId").GeneratedBy.Identity();
Version(x => x.Version).Column("Version").Generated.Never().UnsavedValue("0").Not.Nullable();
OptimisticLock.Version();
Map(x => x.Comment, "clnComment").Length(250).Nullable();
Map(x => x.Description, "clnDescription").Length(250).Nullable();
Map(x => x.IsActive, "clnStatus").Nullable().CustomType<StatusToBoolType>();
}
}
My IUserType Implementation:
public class StatusToBoolType : IUserType
{
public bool IsMutable { get { return false; } }
public Type ReturnedType { get { return typeof(bool); } }
public SqlType[] SqlTypes { get { return new[] { NHibernateUtil.String.SqlType }; } }
public object DeepCopy(object value)
{
return value;
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return cached;
}
public object Disassemble(object value)
{
return value;
}
public new bool Equals(object x, object y)
{
if (ReferenceEquals(x, y)) return true;
if (x == null || y == null) return false;
return x.Equals(y);
}
public int GetHashCode(object x)
{
return x == null ? typeof(bool).GetHashCode() + 473 : x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var obj = NHibernateUtil.String.NullSafeGet(rs, names[0]);
if (obj == null) return true;
var status = (string)obj;
if (status == " ") return true;
if (status == "DI") return false;
throw new Exception(string.Format("Expected data to be either empty or 'DI' but was '{0}'.", status));
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
var parameter = ((IDataParameter) cmd.Parameters[index]);
var active = value == null || (bool) value;
if (active)
parameter.Value = " ";
else
parameter.Value = "DI";
}
}
However this doesn't work. This unit test fails with an inaccurate count.
[TestMethod]
public void GetAllActiveClientsTest()
{
//ACT
var count = Session.QueryOver<Client>()
.Where(x => x.IsActive)
.SelectList(l => l.SelectCount(x => x.Id))
.FutureValue<int>().Value;
//ASSERT
Assert.AreNotEqual(0, count);
Assert.AreEqual(1721, count);
}
The reason it fails is because it generates the following SQL:
SELECT count(this_.clnID) as y0_ FROM Client this_ WHERE this_.clnstatus = #p0;
/* #p0 = ' ' [Type: String (0)] */
But I need it to generate this instead:
SELECT count(this_.clnID) as y0_ FROM Client this_ WHERE (this_.clnstatus = #p0 <b> OR this_.clnstatus IS NULL);</b>
After some debugging I saw that the NullSafeSet() method in my StatusToBoolType class is invoked before the query is generated, so I was able to get around this by writing some hackish code in that method to manipulate the SQL in the cmd.CommandText property.
...
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
var parameter = ((IDataParameter) cmd.Parameters[index]);
var active = value == null || (bool) value;
if (active)
{
parameter.Value = " ";
if (cmd.CommandText.ToUpper().StartsWith("SELECT") == false) return;
var paramindex = cmd.CommandText.IndexOf(parameter.ParameterName);
if (paramindex > 0)
{
// Purpose: change [columnName] = #p0 ==> ([columnName] = #p0 OR [columnName] IS NULL)
paramindex += parameter.ParameterName.Length;
var before = cmd.CommandText.Substring(0, paramindex);
var after = cmd.CommandText.Substring(paramindex);
//look at the text before the '= #p0' and find the column name...
var columnSection = before.Split(new[] {"= " + parameter.ParameterName}, StringSplitOptions.RemoveEmptyEntries).Reverse().First();
var column = columnSection.Substring(columnSection.Trim().LastIndexOf(' ')).Replace("(", "");
var myCommand = string.Format("({0} = {1} OR {0} IS NULL)", column.Trim(), parameter.ParameterName);
paramindex -= (parameter.ParameterName.Length + column.Length + 1);
var orig = before.Substring(0, paramindex);
cmd.CommandText = orig + myCommand + after;
}
}
else
parameter.Value = "DI";
}
But this is NHibernate!!! Hacking the sql statement like this can't possibly be the correct way to handle this? Right?
Because it is a shared legacy database, I can't change the table schema to NOT NULL otherwise I would have just done that, and avoided this scenario.
So finally after all this prelude my question is simply this, where can I tell NHibernate to generate a custom SQL criteria statement for this IUserType?
Thank you all in advance!
Solved it!
After I posted my question I went back to the drawing board, and I came up with a solution that doesn't require hacking the generated SQL in the IUserType implementation. In fact this solution doesn't need the IUserType at all!
Here is what I did.
First, I changed the IsActive column to use a formula to handle the null checking. This fixed my issue with the QueryOver failing, because now everytime NHibernate deals with IsActive property it injects my sql formula to handle null.
The downside to this approach was that after I put in the formula all of my save tests failed. It turns out that formula properties are effectively ReadOnly properties.
So to get around this issue, I added a protected property to the entity to hold the status value from the database.
Next, I changed the IsActive property to set the protected status property to " " or "DI". And finally I changed the FluentMapping to Reveal the protected Status property to NHibernate so that NHibernate can track it. Now that NHibernate is aware of Status it can include it on its INSERT/UPDATE statements.
I am going to include my solution below in case anyone else is interested.
Client class
public class Client
{
...
protected virtual string Status { get; set; }
private bool _isActive;
public virtual bool IsActive
{
get { return _isActive; }
set
{
_isActive = value;
Status = (_isActive) ? " " : "DI";
}
}
}
Changes to Fluent Mapping
public class ClientMapping : CoreEntityMapping<Client>
{
public ClientMapping()
{
....
Map(Reveal.Member<E>("Status"), colName).Length(2);
Map(x => x.IsActive).Formula("case when clnStatus is null then ' ' else clnStatus end");
}
}