Here is the situation:
WebApi v1
Breeze 1.4.7
EF 5.0/NHibernate 3.3.1
What We want: A many to many exposed as a many to one. A client can have multiple countries and a country can have multiple clients. A ClientCountry entity has been created for that purpose.
My mapping looks like this:
Entity Framework:
modelBuilder.Entity<Client>().HasKey(p => p.Id).Property(p=>p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Client>().Property(p => p.Abbreviation);
modelBuilder.Entity<Client>().Property(p => p.ClientSinceDate).IsRequired();
modelBuilder.Entity<Client>().Property(p => p.ClientUntilDate);
modelBuilder.Entity<Client>().Property(p => p.Name).IsRequired();
modelBuilder.Entity<Client>().Property(p => p.Website);
modelBuilder.Entity<Client>().HasMany(p => p.Contacts).WithRequired(p => p.Client).WillCascadeOnDelete(true);
modelBuilder.Entity<Client>().HasMany(p => p.ClientCountries).WithRequired(p => p.Client).WillCascadeOnDelete(true);
modelBuilder.Entity<Contact>().HasKey(p => p.Id).Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Contact>().Property(p => p.Username);
modelBuilder.Entity<Contact>().HasRequired(p => p.Client);
modelBuilder.Entity<Country>().HasKey(p => p.Id).Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Country>().Property(p => p.ValidFrom);
modelBuilder.Entity<Country>().Property(p => p.ValidTo);
modelBuilder.Entity<Country>().Property(p => p.Code);
modelBuilder.Entity<Country>().Property(p => p.DefaultLabel);
modelBuilder.Entity<Country>().Property(p => p.Description);
modelBuilder.Entity<Country>().Property(p => p.DisplayOrder);
modelBuilder.Entity<ClientCountry>().HasKey(p => p.Id).Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<ClientCountry>().Property(p => p.ValidFrom);
modelBuilder.Entity<ClientCountry>().Property(p => p.ValidTo);
modelBuilder.Entity<ClientCountry>().HasRequired(p => p.Client);
modelBuilder.Entity<ClientCountry>().HasRequired(p => p.Country);
NHibernate:
public class BaseMapping<T> : ClassMapping<T> where T : BaseEntity
{
public BaseMapping()
{
this.Lazy(true);
Id(x => x.Id, map => { map.Generator(Generators.GuidComb); });
}
}
public class ClientMap : BaseMapping<Client>
{
public ClientMap()
{
this.Property(x => x.Name);
this.Property(x => x.Abbreviation);
this.Property(x => x.ClientSinceDate, map => map.NotNullable(true));
this.Property(x => x.ClientUntilDate);
this.Property(x => x.City);
this.Property(x => x.Website);
this.Bag<Department>(x => x.Departments, colmap =>
{
colmap.Key(x => x.Column("ClientId"));
colmap.Inverse(true);
colmap.Cascade(Cascade.All | Cascade.DeleteOrphans);
}, map =>
{
map.OneToMany();
});
this.Bag<ClientCountry>(x => x.ClientCountries, colmap =>
{
colmap.Cascade(Cascade.All | Cascade.DeleteOrphans);
colmap.Key(p => p.Column("ClientId"));
colmap.Inverse(true);
}, map =>
{
map.OneToMany();
});
this.Bag<Contact>(x => x.Contacts, colmap =>
{
colmap.Key(x => x.Column("ClientId"));
colmap.Cascade(Cascade.All | Cascade.DeleteOrphans);
}, map =>
{
map.OneToMany();
});
}
}
public class CountryMap : BusinessRefEntityMapping<Country>
{
public CountryMap()
{
Bag<ClientCountry>(x => x.ClientCountries, colmap =>
{
colmap.Cascade(Cascade.All);
colmap.Key(p => p.Column("CountryId"));
}, map =>
{
map.OneToMany();
});
}
}
public class ClientCountryMap : BaseMapping<ClientCountry>
{
public ClientCountryMap()
{
Property(x => x.ValidFrom);
Property(x => x.ValidTo);
Property(x => x.ClientId, map =>
{
map.Column("ClientId");
map.Insert(false);
map.Update(false);
map.NotNullable(true);
});
Property(x => x.CountryId, map =>
{
map.Column("CountryId");
map.Insert(false);
map.Update(false);
map.NotNullable(true);
});
ManyToOne<Client>(x => x.Client, map =>
{
map.Column("ClientId");
map.Cascade(Cascade.All);
map.Insert(true);
map.Update(true);
map.NotNullable(true);
});
ManyToOne<Country>(x => x.Country, map =>
{
map.Column("CountryId");
map.Cascade(Cascade.All);
map.Insert(true);
map.Update(true);
map.NotNullable(true);
});
}
}
The js code:
$scope.create = function (index) {
var c = $scope.clients[index];
var newClientCountry = breezeService.manager.createEntity('ClientCountry', {
ValidFrom: Date(2013, 01, 01),
ValidTo: Date(2015, 01, 01),
Client: c,
Country: country,
});
breezeService.manager.saveChanges()
.then(function (data) {
$log.info('client created');
})
.fail(function (dat) {
$log.error('save client failed:' + data)
})
}
The issue: With NHibernate, saving a clientcountry results in this error message :"not-null property references a null or transient value CdT.EAI.DAL.ClientCountry.Country". With EF, all works as expected.
Is there something wrong with my code?
So, since there is no feedback yet, here's what we did to make it (more or less) work:
First, to be able to save a new ClientCountry(many to many with exposed junction table), we must do this:
public class ClientCountryMap : BaseMapping<ClientCountry>
{
public ClientCountryMap()
{
Property(x => x.ValidFrom);
Property(x => x.ValidTo);
Property(x => x.ClientId, map =>
{
map.Column("ClientId");
map.Insert(true);
map.Update(true);
map.NotNullable(true);
});
Property(x => x.CountryId, map =>
{
map.Column("CountryId");
map.Insert(true);
map.Update(true);
map.NotNullable(true);
});
ManyToOne<Client>(x => x.Client, map =>
{
map.Column("ClientId");
map.Cascade(Cascade.All);
map.Insert(false);
map.Update(false);
map.NotNullable(true);
});
ManyToOne<Country>(x => x.Country, map =>
{
map.Column("CountryId");
map.Cascade(Cascade.All);
map.Insert(false);
map.Update(false);
map.NotNullable(true);
});
}
}
The difference here is that the insert/update are inverted. Insert(true) and Update(true) must be set on the foreign key mapping and not the association. It differs from the breeze doc on that point.
The second is that Inverse(true) must be set on each collection association or all your collections will be deleted when updating the parent entity.
With that,changes, it seems to work as expected.
ps: It is fixed in the latest version (>1.4.8). The comments above don't apply anymore.
Related
i have three tables tbl_admin_group ,tbl_admin_role, tbl_admin_groupRole the primary key of group and role are foreign keys in grouprole.
now i have a form in which i want to display foreign keys in dropdownlist first
for tbl_admin_role. image link given below
Controller Action Method
public ActionResult GetGroupRole()
{
dbcontext.Configuration.ProxyCreationEnabled = false;
List<TBL_ADMIN_GROUP_ROLE> lst = dbcontext.TBL_ADMIN_GROUP_ROLE.Where(x => x.IsDeleted == 0).ToList();
also try this
//List<TBL_ADMIN_GROUP_ROLE> lst = dbcontext.TBL_ADMIN_GROUP_ROLE.Include(r => r.TBL_ADMIN_ROLE).Include(g => g.TBL_ADMIN_GROUP).ToList();
ViewData["rolesList"] = lst;
return View(lst);
}
Razor view
#(Html.Kendo().Grid<TBL_ADMIN_GROUP_ROLE>().Name("Grid")
.Columns(columns =>
{
columns.Template(t => { }).Title("S.No").ClientTemplate("#= renderNumber(data) #");
columns.ForeignKey(p => p.RoleID, (System.Collections.IEnumerable)ViewData["rolesList"], "RoleID", "RoleName")
.Title("Role").Width(200).EditorTemplateName("RolesDropDown");
columns.Bound(gp => gp.GroupID).Width(200).Title("Group ID");
columns.Command(command => { command.Edit(); command.Destroy(); }).Title("Actions");
})
.DataSource(dataSource => dataSource
.Ajax()
.PageSize(10)
.Model(model => model.Id(gp => gp.GroupRoleID))
.Read(read => read.Action("GroupRole_Read", "Security"))
.Update(up => up.Action("UpdateGroupRole", "Security"))
.Destroy(update => update.Action("DeleteGroupRole", "Security")))
.Pageable(pageable => pageable
.PageSizes(true)
.ButtonCount(5))
.Selectable()
.Sortable()
.Editable(ed => ed.Mode(GridEditMode.PopUp).TemplateName("Update Group Role"))
.Events(e => e.Edit("onEdit"))
.Events(ev => ev.DataBound("resetRowNumber"))
)
i also trying with editor template but failed. please help me . thank in advance.
Editor Template
#model int
#(Html.Kendo().DropDownListFor(m => m)
.AutoBind(false)
.OptionLabel("Select Role...")
.DataTextField("RoleName")
.DataValueField("RoleID")
.DataSource(dataSource =>
{
dataSource.Read(read => read.Action("GetRoles", "Security"))
.ServerFiltering(true);
})
)
enter image description here
I want my "tag_post" table to be created in "article" schema but it's created in "public" schema.
List(x => x.Tags, l =>
{
l.Where("deleted = 0");
l.Key(k =>
{
k.Column("post_id");
k.NotNullable(true);
});
Schema(Constants.DatabaseSchemaNames.Article);
l.Table("tag_post");
}, x =>
{
x.ManyToMany(m => m.Column("tag_id"));
});
I have never used mapping by code, but i assume this is the solution:
List(x => x.Students, l =>
{
l.Where("deleted = 0");
l.Key(k =>
{
k.Column("post_id");
k.NotNullable(true);
});
l.Schema(Constants.DatabaseSchemaNames.Article);
l.Table("tag_post");
}, x =>
{
x.ManyToMany(m => m.Column("tag_id"));
});
I'm having a problem adding a child record in my hierarchical grid. It won't pass over the HeaderId from the parent in the grid.
Can anyone spot an issue, or am I trying to do something that isn't possible?
Thanks.
Here's the controller action:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult BillDetail_Create(BillDetail billDetail, int billHeaderId)
{
if (billHeaderId == 0)
{
ModelState.AddModelError("billHeaderID", "add bill header first");
}
if (billDetail != null && ModelState.IsValid)
{
var target = new BillDetail
{
Category = billDetail.Category,
Description = billDetail.Description,
Amount = billDetail.Amount,
BillHeaderId = billHeaderId,
BillDetailId = SessionBillDetails.Max(d => d.BillDetailId) + 1
};
//Get next Id in sequence
billDetail.BillDetailId = target.BillDetailId;
SessionBillDetails.Add(target);
}
return Json(new[] { billDetail }.ToDataSourceResult(new DataSourceRequest(), ModelState));
}
And here's the view:
#(Html.Kendo().Grid<BillHeader>()
.Name("BillHeaders")
.Columns(columns =>
{
columns.Bound(h => h.BillHeaderId);
columns.Bound(h => h.Category);
columns.Bound(h => h.Description);
columns.Bound(h => h.Amount);
})
.Pageable()
.Selectable(selectable => selectable
.Mode(GridSelectionMode.Multiple)
.Type(GridSelectionType.Row))
.DataSource(dataSource => dataSource
.Ajax()
.PageSize(6)
.Events(events => events.Error("error_handler"))
.Read(read => read.Action("BillHeaders_Read", "Bill"))
)
.Events(events => events.DataBound("dataBound"))
.ClientDetailTemplateId("BillDetails")
)
<script id="BillDetails" type="text/kendo-tmpl">
#(Html.Kendo().Grid<BillDetail>()
.Name("BillDetails_#=BillHeaderId#")
.Columns(columns =>
{
columns.Bound(d => d.BillHeaderId).Width(50);
columns.Bound(d => d.BillDetailId).Width(70);
columns.Bound(d => d.Category).Width(70);
columns.Bound(d => d.Description).Width(150);
columns.Bound(d => d.Amount).Width(80);
columns.Command(command =>
{
command.Edit();
command.Destroy();
}).Width(75);
})
.DataSource(dataSource => dataSource
.Ajax()
.PageSize(10)
.Model(model =>
{
model.Id(d => d.BillDetailId);
model.Field(d => d.BillDetailId).Editable(false);
})
.Events(events => events.Error("error_handler"))
.Read(read => read.Action("BillDetails_Read", "Bill", new { billHeaderId = "#=BillHeaderId#" }))
.Update(update => update.Action("BillDetail_Update", "Bill"))
**.Create(create => create.Action("BillDetail_Create", "Bill", new { billHeaderId = "#=BillHeaderId#" }))**
.Destroy(destroy => destroy.Action("BillDetail_Destroy", "Bill")))
.Pageable()
.ToolBar(tools => tools.Create())
.ToClientTemplate()
)
</script>
Managed to finally fix this. Unbelievable really....
I named the parameter in my controller (and view) to be "id"
So Controller:
public ActionResult BillDetail_Create(BillDetail billDetail, int id)
And View:
.Read(read => read.Action("BillDetails_Read", "Bill", new { id = "#=BillHeaderId#" }))
.Update(update => update.Action("BillDetail_Update", "Bill"))
.Create(create => create.Action("BillDetail_Create", "Bill", new { id = "#=BillHeaderId#" }))
.Destroy(destroy => destroy.Action("BillDetail_Destroy", "Bill")))
For a better explanation of why that worked:
This can occur if the BillDetail class has a property with the same name. In this case the MVC model binder will override the value sent with the request route values with the value sent as form data for the grid model. If this is the case then the simplest option to avoid the problem would be to rename the parameter. that is why renaming to ID worked.
I have query in HQL which works good:
var x =_session.CreateQuery("SELECT r FROM NHFolder f JOIN f.DocumentComputedRights r WHERE f.Id = " + rightsHolder.Id + " AND r.OrganisationalUnit.Id=" + person.Id);
var right = x.UniqueResult<NHDocumentComputedRight>();
Basically I receive NHDocumentComputedRight instance.
I've tried to implement the same query in QueryOver. I did this:
var right = _session.QueryOver<NHFolder>().JoinAlias(b => b.DocumentComputedRights, () => cp).Where(h => h.Id == rightsHolder.Id && cp.OrganisationalUnit.Id == person.Id)
.Select(u => cp).List<NHDocumentComputedRight>();
But I get null reference exception.
How can I implement this query in QueryOver?
Update (added mappings) - NHibernate 3.2:
public class FolderMapping: ClassMapping<NHFolder>
{
public FolderMapping()
{
Table("Folders");
Id(x => x.Id, map =>
{
map.Generator(IdGeneratorSelector.CreateGenerator());
});
//more not important properties...
Set(x => x.DocumentComputedRights, v =>
{
v.Table("DocumentComputedRightsFolder");
v.Cascade(Cascade.All | Cascade.DeleteOrphans);
v.Fetch(CollectionFetchMode.Subselect);
v.Lazy(CollectionLazy.Lazy);
}, h => h.ManyToMany());
Version(x => x.Version, map => map.Generated(VersionGeneration.Never));
}
}
public class DocumentComputedRightMapping : ClassMapping<NHDocumentComputedRight>
{
public DocumentComputedRightMapping()
{
Table("DocumentComputedRights");
Id(x => x.Id, map =>
{
map.Generator(IdGeneratorSelector.CreateGenerator());
});
//more not important properties...
ManyToOne(x => x.OrganisationalUnit, map =>
{
map.Column("OrganisationalUnit");
map.NotNullable(false);
map.Cascade(Cascade.None);
});
}
}
public class OrganisationUnitMapping : ClassMapping<NHOrganisationalUnit>
{
public OrganisationUnitMapping()
{
Table("OrganisationalUnits");
Id(x => x.Id, map =>
{
map.Generator(IdGeneratorSelector.CreateGenerator());
});
//more not important properties...
}
}
Thanks
AFAIK criteria/queryOver can only return the entity it was created for (NHFolder in your example) or columns which are set to entity with aliastobean. you could do a correlated subquery instead.
var subquery = QueryOver.Of<NHFolder>()
.JoinAlias(b => b.DocumentComputedRights, () => cp)
.Where(h => h.Id == rightsHolder.Id && cp.OrganisationalUnit.Id == person.Id)
.Select(u => cp.Id);
var right = _session.QueryOver<NHDocumentComputedRight>()
.WithSubquery.Where(r => r.Id).Eq(subquery)
.SingleOrDefault<NHDocumentComputedRight>();
I think you have a problem with the select statement, have you tried something like this:
var right = _session.QueryOver<NHFolder>()
.JoinAlias(b => b.DocumentComputedRights, () => cp)
.Select(x => x.DocumentComputedRights)
.Where(h => h.Id == rightsHolder.Id && cp.OrganisationalUnit.Id == person.Id)
.List<NHDocumentComputedRight>();
This is what is working for me so it should work in you case as well.
I would guess that the main reason behind the problem is the lack of proper overload on the Select method. In reality you would like to write it like this:
.JoinAlias(b => b.DocumentComputedRights, () => cp)
.Select(() => cp)
but the Expression<Func<object>> is not there. Hopefully it's going to be included in the next version.
Is there a way to set the column prefix for a component in fluent. For example:
public class SomeClassMap : ClassMap < SomeClass >
{
public SomeClassMap()
{
CreateMap();
}
private void CreateMap()
{
WithTable("Class");
Id(x => x.Id).GeneratedBy.Guid();
Map(x => x.Name).WithLengthOf(100);
Component<SomeComponent>(x => x.somecomponent, m =>
{
m.Map(x => x.Name).SetAttribute("column", "SomeComponentName");
m.Map(x => x.Summary).SetAttribute("column", "SomeComponentSummary");
.... etc ...
}
);
Is there a way to set "SomeComponent" prefixes instead of having to define them in a SetAttribute?
There's nothing implicit. AutoMapping does it, but not regular mapping. I've created an issue so you can track the status of this.
There is some good information here: http://nhforge.org/blogs/nhibernate/archive/2008/09/06/a-fluent-interface-to-nhibernate-part-2-value-objects.aspx that seems to be what you are wanting to do.
In particular the Action method demonstrated in this sample:
public class EmployeeMap : ClassMap<Employee>
{
private Action<ComponentPart<Address>> MapAddress(string columnPrefix)
{
return a =>
{
a.Map(x => x.AddressLine1, columnPrefix + "AddressLine1");
a.Map(x => x.AddressLine2, columnPrefix + "AddressLine2");
a.Map(x => x.PostalCode, columnPrefix + "PostalCode");
a.Map(x => x.City, columnPrefix + "City");
a.Map(x => x.Country, columnPrefix + "Country");
};
}
public EmployeeMap()
{
Id(x => x.Id);
Map(x => x.FirstName).CanNotBeNull().WithLengthOf(20);
Map(x => x.LastName).CanNotBeNull().WithLengthOf(20);
Component<Address>(x => x.HomeAddress, MapAddress("Home_"));
Component<Address>(x => x.WorkAddress, MapAddress("Work_"));
}
}