I have a running database with a lot of prodcut data and I'm trying to get a (Fluent) NHibernate mapping for the structure below.
But running the code will result in an error:
Foreign key (FK1F94D86A1A0EC427:Product [ProductDet1_id])) must have same number of columns as the referenced primary key (ProductDet1 [ProductNumber, ProductionLine])
So something went wrong during mapping but can't figure out what it is. Is there somebody who can get me out of this problem? :-)
(Ofcource I let out all specific details in the tables, just to make it readable)
We hava a product table where the productnumber in combination with production line is unique. Every Product can have only one ProductDet1, ProductDet2 ... etc
MSSQL Product Table:
CREATE TABLE [dbo].[Product] (
[Id] INT NOT NULL IDENTITY,
[ProductNumber] INT NOT NULL,
[ProductionLine] INT NOT NULL,
CONSTRAINT [AK_Product_ProductNumber] UNIQUE ([ProductNumber], [ProductionLine]),
CONSTRAINT [PK_Product] PRIMARY KEY ([Id])
);
MSSQL ProductDet1 and ProductDet2 Table:
CREATE TABLE [dbo].[ProductDet1] (
[ProductNumber] INT NOT NULL,
[ProductionLine] INT NOT NULL,
[TheValue] VARCHAR (15) NULL,
CONSTRAINT [FK_ProductDet1_Product] FOREIGN KEY ([ProductNumber], [ProductionLine]) REFERENCES [Product]([ProductNumber],[ProductionLine]),
CONSTRAINT [AK_ProductDet1_ProductNumber] UNIQUE ([ProductNumber], [ProductionLine])
);
GO
CREATE INDEX [IX_ProductDet1_ProductNumber] ON [dbo].[ProductDet1] ([ProductNumber])
GO
CREATE INDEX [IX_ProductDet1_ProductionLine] ON [dbo].[ProductDet1] ([ProductionLine])
C# product class:
public class Product
{
public Product ()
{
ProductDet1 = new ProductDet1();
ProductDet2 = new ProductDet2();
}
public virtual int Id { get; set; }
public virtual int ProductionLine { get; set; }
public virtual int ProductNumber { get; set; }
public virtual string ProductName { get; set; }
public virtual ProductDet1 ProductDet1 { get; set; }
public virtual ProductDet2 ProductDet2 { get; set; }
public override bool Equals(object obj)
{
// If parameter is null return false.
if (obj == null)
{
return false;
}
// If parameter cannot be cast to Reference return false.
var product = obj as Product;
if (product == null)
{
return false;
}
// Return true if the fields match:
return this.ProductionLine == product.ProductionLine && this.ProductNumber == product.ProductNumber;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
C# product Det1 and product Det2 class:
public class ProductDet1
{
public virtual int Id { get; set; }
public virtual int ProductionLine { get; set; }
public virtual int ProductNumber { get; set; }
public virtual string TheValue { get; set; }
public virtual Product Product { get; set; }
public override bool Equals(object obj)
{
// If parameter is null return false.
if (obj == null)
{
return false;
}
// If parameter cannot be cast to Reference return false.
var product = obj as ProductCRT;
if (product == null)
{
return false;
}
// Return true if the fields match:
return this.ProductionLine == product.ProductionLine && this.ProductNumber == product.ProductNumber;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
Product Map:
public class ProductEntityMap : ClassMap<Product>
{
public ProductEntityMap()
{
Id(x => x.Id);
Map(x => x.ProductNumber);
Map(x => x.ProductionLine);
Map(x => x.ProductName);
References(x => x.ProductDet1).Cascade.All().Not.LazyLoad();
References(x => x.ProductDet2).Cascade.All().Not.LazyLoad();
}
}
Product Det 1 and Det 2 Map:
public class ProductDet1EntityMap : ClassMap<ProductDet1>
{
public ProductDet1EntityMap()
{
CompositeId().KeyProperty(x => x.ProductNumber).KeyProperty(x => x.ProductionLine);
Map(x => x.TheValue);
}
}
a slightly different class structure and mapping which hide the complexity of the det class. It essentially sets the columns used on the References()
public class Product
{
private ProductDet ProductDet1 { get; set; }
private ProductDet ProductDet2 { get; set; }
protected Product() { } // make NHibernate happy
public Product(int productionLine, int productNumber) : this(new ProductKey(productionLine, productNumber)) { }
public Product(ProductKey key)
{
Key = key;
ProductDet1 = new ProductDet { ProductKey = Key };
ProductDet2 = new ProductDet { ProductKey = Key };
}
public virtual int ID { get; protected set; }
public virtual ProductKey Key { get; protected set; }
public virtual string Det1
{
get { return ProductDet1.Value; }
set { ProductDet1.Value = value; }
}
public virtual string Det2
{
get { return ProductDet2.Value; }
set { ProductDet2.Value = value; }
}
}
public class ProductKey
{
protected ProductKey() { } // make NHibernate happy
public ProductKey(int productionLine, int productNumber)
{
ProductionLine = productionLine;
ProductNumber = productNumber;
}
public virtual int ProductionLine { get; private set; }
public virtual int ProductNumber { get; private set; }
public override bool Equals(object obj)
{
var other = obj as ProductKey;
return other != null && other.ProductionLine == this.ProductionLine && other.ProductNumber == this.ProductNumber;
}
public override int GetHashCode()
{
return (ProductionLine << 16) + ProductNumber;
}
}
public class ProductDet
{
public virtual ProductKey ProductKey { get; set; }
public virtual string Value { get; set; }
}
public class ProductMap : ClassMap<Product>
{
public ProductMap()
{
Id(x => x.ID);
Component(x => x.Key, c =>
{
c.Map(k => k.ProductionLine).UniqueKey("product_key");
c.Map(k => k.ProductNumber).UniqueKey("product_key");
});
MapProductDetReference("ProductDet1");
MapProductDetReference("ProductDet2");
}
private void MapProductDetReference(string entityName)
{
References(Reveal.Member<Product, ProductDet>(entityName))
.Columns("ProductionLine", "ProductNumber")
.ReadOnly()
.EntityName(entityName)
.Cascade.All()
.Fetch.Join()
.Not.LazyLoad();
}
}
public abstract class ProductDetMap : ClassMap<ProductDet>
{
public ProductDetMap()
{
CompositeId(x => x.ProductKey)
.KeyProperty(k => k.ProductionLine)
.KeyProperty(k => k.ProductNumber);
Map(x => x.Value, "TheValue");
}
}
public class ProductDet1Map : ProductDetMap
{
public ProductDet1Map()
{
EntityName("ProductDet1");
Table("ProductDet1");
}
}
public class ProductDet2Map : ProductDetMap
{
public ProductDet2Map()
{
EntityName("ProductDet2");
Table("ProductDet2");
}
}
Related
This question already has answers here:
Recursively Get Properties & Child Properties Of A Class
(5 answers)
Closed 2 years ago.
I am trying to write an universal search to use for all objects.
I have this code, which is working fine to search in just one object's properties, but I would also like to search also in properties in related objects.
Eg. I have these Models/Objects
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Address{ get; set; }
public ICollection<Contract> Contracts { get; set; }
}
public class Contract
{
public int Id { get; set; }
public DateTime From{ get; set; }
public DateTime To{ get; set; }
public string Comment{ get; set; }
public int CustomerId { get; set; }
[ForeignKey("CustomerId")]
public Customer Customer { get; set; }
}
and I want to search if any of properties contains some a string eg. "Peter", I will call it this way:
string searchString = "Peter";
var customers = db.Customers
.Include(x => x.Contracts)
.WhereAnyPropertiesOfSimilarTypeContains(searchString);
this code will check if any properties of 'Customer' contains string "Peter".
But I would also need to check if the related model 'Contract' contains "Peter.
public static class EntityHelper
{
public static IQueryable<TEntity> WhereAnyPropertiesOfSimilarTypeContains<TEntity, TProperty>(this IQueryable<TEntity> query, TProperty value)
{
var param = Expression.Parameter(typeof(TEntity));
var predicate = PredicateBuilder.False<TEntity>(); //--- True to equal
var entityFields = GetEntityFieldsToCompareTo<TEntity, TProperty>();
foreach (var fieldName in entityFields)
{
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var predicateToAdd = Expression.Lambda<Func<TEntity, bool>>(
Expression.Call(
Expression.PropertyOrField(param, fieldName), method,
Expression.Constant(value)), param);
predicate = predicate.Or(predicateToAdd); //--- And to equal
}
return query.Where(predicate);
}
// TODO: You'll need to find out what fields are actually ones you would want to compare on.
// This might involve stripping out properties marked with [NotMapped] attributes, for
// for example.
public static IEnumerable<string> GetEntityFieldsToCompareTo<TEntity, TProperty>()
{
Type entityType = typeof(TEntity);
Type propertyType = typeof(TProperty);
var fields = entityType.GetFields()
.Where(f => f.FieldType == propertyType)
.Select(f => f.Name);
var properties = entityType.GetProperties()
.Where(p => p.PropertyType == propertyType)
.Select(p => p.Name);
return fields.Concat(properties);
}
}
Thanks.
After reread the question. I don't know what are you trying, but here I put the idea I have what are you looking for.
public class Customer : AbstractEntity
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Address { get; set; }
public ICollection<Contract> Contracts { get; set; }
}
public class Contract : AbstractEntity
{
//what property here can be string "Peter"? Comments?
//what are you trying?
public int Id { get; set; }
public DateTime From { get; set; }
public DateTime To { get; set; }
public string Comment { get; set; }
public int CustomerId { get; set; }
[ForeignKey("CustomerId")]
public Customer Customer { get; set; }
}
public abstract class AbstractEntity
{
//this method can be used to preselect properties you want
protected virtual Tuple<bool, ICollection<PropertyInfo>> PropertyCollector()
{
return new Tuple<bool, ICollection<PropertyInfo>>(false, null);
}
public IEnumerable<Tuple<Type, object>> GetRowValues()
{
foreach (var prop in GetRows())
{
yield return new Tuple<Type, object>(prop.PropertyType, prop.GetValue(this));
}
}
public ICollection<PropertyInfo> GetRows()
{
var tuple = PropertyCollector();
ISet<PropertyInfo> pInfo;
if (tuple.Item1)
{
pInfo = new HashSet<PropertyInfo>(tuple.Item2);
}
else //search all non virtual, private, protected properties, "following POCO scheme"
{
pInfo = new HashSet<PropertyInfo>();
foreach (var prop in GetType().GetProperties())
{
foreach (var access in prop.GetAccessors())
{
if ((!access.IsVirtual && !access.IsPrivate) && (prop.CanWrite && prop.CanRead))
{
pInfo.Add(prop);
}
}
}
}
return pInfo;
}
}
public static class Searchs
{
public static ICollection<object> ObjectsWithStringFound(ICollection<Customer> customers, string toBeFound)
{
var objs = new List<object>();
foreach (var cust in customers)
{
var strings = cust.GetRowValues().Where(tpl => tpl.Item1 == typeof(string)).Select(tpl => tpl.Item2);
var contracts = cust.GetRowValues().Where(tpl => tpl.Item2 is IEnumerable<Contract>).Select(tpl => tpl.Item2);
if (strings.Any(str => str == toBeFound))
{
objs.Add(cust);
}
else if (contracts.Any(ctr => ((IEnumerable<Contract>)ctr).!!!!!!!!! == toBeFound))
{ //What I suppose I must "match" with "Peter"??!?!
objs.Add(contracts.First(ctr => ((IEnumerable<Contract>)ctr).!!!!!!!!! == toBeFound));
}
}
return objs;
}
}
I think we aren't understanding each other.
I use AutoMapper 8.1.0 in a asp.net core project. I have an Automapper mapping that doesn't work as I expected. I reproduced the configuration so you can test it by yourself. So I have an ExpenseReport with a collection of ExpenseReportItem and this one with another collection. I have to keep the data of eTaxCollection after the mapping, but they are lost in the process.
So the question is why values of eTaxCollections are lost after calling _mapper.Map(vmodel, model) and how can I keep them?
The ignore attribute don't work. I also tried UseDestinationValue(). I lost 2 days trying to figure it out and I'm exhausted.
public void WeatherForecasts()
{
int[] excludeTaxes = new int[] { 2 };
var vmodel = new ExpenseReportCreateEditModel();
vmodel.Expenses.Add(new ExpenseReportItemModel()
{
ExcludeTaxIds = excludeTaxes,
Total = 12,
Id = 1
});
// fetch from bd
var model = new ExpenseReport();
// values will be lost after _mapper.Map...
var eTaxCollections = new HashSet<ExcludeExpenseReportItemTax>();
eTaxCollections.Add(new ExcludeExpenseReportItemTax()
{
TaxId = 1,
ExpenseReportItemId = 1
});
model.Items.Add(new ExpenseReportItem()
{
ExcludeTaxes = eTaxCollections,
ExpenseReportId = 1,
Id = 9
});
_mapper.Map(vmodel, model);
}
public class ExpenseReportCreateEditModelProfile : Profile
{
public ExpenseReportCreateEditModelProfile()
{
CreateMap<ExpenseReportCreateEditModel, ExpenseReport>()
.ForMember(d => d.Items, s => s.MapFrom(m => m.Expenses));
}
}
public class ExpenseReportItemModelProfile : Profile
{
public ExpenseReportItemModelProfile()
{
CreateMap<ExpenseReportItemModel, ExpenseReportItem>()
.ForMember(d => d.ExcludeTaxes, s => s.Ignore()); // <<<==== data are lost
}
}
public class ExpenseReportCreateEditModel
{
public int Id { get; set; }
public ICollection<ExpenseReportItemModel> Expenses { get; set; }
public ExpenseReportCreateEditModel()
{
Expenses = new HashSet<ExpenseReportItemModel>();
}
}
public class ExpenseReportItemModel
{
public int Id { get; set; }
public ICollection<int> ExcludeTaxIds { get; set; }
public decimal Total { get; set; }
public ExpenseReportItemModel()
{
ExcludeTaxIds = new HashSet<int>();
}
}
public class ExpenseReport
{
public int Id { get; set; }
public virtual ICollection<ExpenseReportItem> Items { get; set; }
public ExpenseReport()
{
Items = new HashSet<ExpenseReportItem>();
}
}
public class ExpenseReportItem
{
public int Id { get; set; }
public int ExpenseReportId { get; set; }
public virtual ICollection<ExcludeExpenseReportItemTax> ExcludeTaxes { get; set; }
public ExpenseReportItem()
{
ExcludeTaxes = new HashSet<ExcludeExpenseReportItemTax>();
}
}
public class ExcludeExpenseReportItemTax
{
public int ExpenseReportItemId { get; set; }
public virtual ExpenseReportItem ExpenseReportItem { get; set; }
public int TaxId { get; set; }
}
Thank you for any help
Edit
I execute the execution plan and perhaps this is the problem:
$typeMapDestination = ($dest ?? .New WebApplication1.Controllers.SampleDataController+ExpenseReportItem());
This is only way I can lost the values.
I have to find a solution now
Here the complete execution plan :
.If ($src == null) {
.Default(WebApplication1.Controllers.SampleDataController+ExpenseReportItem)
} .Else {
.Block() {
$typeMapDestination = ($dest ?? .New WebApplication1.Controllers.SampleDataController+ExpenseReportItem());
.Try {
.Block(System.Int32 $resolvedValue) {
.Block() {
$resolvedValue = .If (
$src == null || False
) {
.Default(System.Int32)
} .Else {
$src.Id
};
$typeMapDestination.Id = $resolvedValue
}
}
} .Catch (System.Exception $ex) {
.Block() {
.Throw .New AutoMapper.AutoMapperMappingException(
"Error mapping types.",
$ex,
.Constant<AutoMapper.TypePair>(AutoMapper.TypePair),
.Constant<AutoMapper.TypeMap>(AutoMapper.TypeMap),
.Constant<AutoMapper.PropertyMap>(AutoMapper.PropertyMap));
.Default(System.Int32)
}
};
$typeMapDestination
}
}
this Is my Class: TimePart.
public class TimePart : IEntity, IComposite<TimePart>
{
[Key()]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
public long? ParentId { get; set; }
public virtual TimePart Parent { get; set; }
public virtual List<TimePart> Children { get; set; }
.......+some other properties
public TimePart()
{
Children = new List<TimePart>();
}
public virtual TimePart Root
{
get
{
if (Parent == null)
return this;
TimePart root = Parent;
int levels = 0;
while (root.Parent != null && levels < 50)
{
root = root.Parent;
levels++;
}
return root;
}
}
}
here is my dbContext
public class DbContextFlex : DbContext, IDisposable
{
public DbContextFlex(string connectionString)
: base(connectionString)
{
((IObjectContextAdapter)this).ObjectContext.CommandTimeout = 300;
Configuration.ProxyCreationEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<TimePart>().HasOptional(m => m.Parent)
.WithMany(m => m.Children)
.HasForeignKey(m => m.ParentId);
}
public DbSet<TimePart> Timeparts { get; set; }
.....with other dbsets.....
Here is my Problem. the following line takes about 26 seconds to load 90 rows with there childeren and the childeren there childeren....
var tp = Helpers.DbContext.Timeparts.ToList().Single(t => t.Id == simpleTp.Id);
If i remove the ToList and change my code like :
var tp = Helpers.DbContext.Timeparts.Single(t => t.Id == simpleTp.Id);
I don't get al the childeren and theire childeren....
Entity and Component has one-to-many relationship. Entity stored in db correctly, with all components. But when I trying load it back this Entity in Child method Components not loads. If i try use lazy loading, code failing on entity.GetComponent because of Entity.Components isn't initialized. After exception Components are initialized and has zero elements. If I disable lazy loading, Components are initialized and has zero elements. I wrote example for building one-to-many relationship and using lazy initialization, and it works fine.
public static void Main(string[] args)
{
Parent();
Child();
}
private static void Child()
{
using (var db = new EntitiesContext())
{
var entities = from entity in db.Entities
select entity;
foreach (Entity entity in entities)
{
Position pos = entity.GetComponent<Position>();
Core core = entity.GetComponent<Core>();
}
}
}
private static void Parent()
{
Entity entity = new Entity();
entity.AddComponent(new Position(10, 10));
entity.AddComponent(new ObjectName("Entity" + 1));
entity.AddComponent(new Core(100));
using (var db = new EntitiesContext())
{
db.Entities.Add(entity);
db.SaveChanges();
}
}
public class EntitiesContext : DbContext
{
public DbSet<TypeMaskPair> MappedTypes { get; set; }
public DbSet<Entity> Entities { get; set; }
public EntitiesContext()
: base("EntitiesDb")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity>()
.HasMany(entity => entity.Components)
.WithRequired(component => component.EntityObj)
.HasForeignKey(component => component.EntityId)
.WillCascadeOnDelete();
//this.Configuration.LazyLoadingEnabled = false;
}
}
public abstract class Component
{
[Key]
public int Id { get; set; }
[Required]
public int EntityId { get; set; }
[Required]
public virtual Entity EntityObj { get; set; }
private static DbMasksMapper componentsMap;
static Component()
{
componentsMap = new DbMasksMapper(typeof(Component));
}
public TypeMask GetMask()
{
return componentsMap.GetMask(this.GetType());
}
public static TypeMask GetMask<T>() where T : Component
{
return componentsMap.GetMask(typeof(T));
}
}
public class Entity
{
[Key]
public int Id { get; set; }
public virtual List<Component> Components { get; set; }
[NotMapped]
public TypeMask Mask { get; private set; }
public string TypeMaskString
{
get{ return Mask.ToString(); }
set{ Mask = new TypeMask(value); }
}
public Entity()
{
Components = new List<Component>();
Mask = new TypeMask();
}
public void AddComponent(Component component)
{
Components.Add(component);
component.EntityObj = this;
Mask |= component.GetMask();
}
public void DeleteComponent(TypeMask componentMask)
{
if (ContainsComponent(componentMask))
{
int removeIndex = Components.FindIndex(c => c.GetMask() == componentMask);
Components.RemoveAt(removeIndex);
Mask &= ~componentMask;
}
}
public Component GetComponent(TypeMask componentMask)
{
return Components.Find(c => c.GetMask() == componentMask);
}
public T GetComponent<T>() where T : Component
{
return (T) GetComponent(Component.GetMask<T>());
}
public bool ContainsComponent<T>() where T : Component
{
return ContainsComponent(Component.GetMask<T>());
}
public bool ContainsComponent(TypeMask componentMask)
{
return (Mask & componentMask) == componentMask;
}
}
class Position : Component
{
public Position(int x = 0, int y = 0)
{
X = x;
Y = y;
}
public int X { get; set; }
public int Y { get; set; }
}
class Cargo : Component
{
public Cargo(int capacity = 0)
{
Capacity = capacity;
}
public int Capacity { get; set; }
}
class Core : Component
{
public Core(int power = 0)
{
Power = power;
}
public int Power { get; set; }
}
class ObjectName : Component
{
public ObjectName(string name = "")
{
Name = name;
}
public string Name { get; set; }
}
I saw similar questions, but didn't found any answer.
Where is a mistake?
Solution
All works after I wrote default constructor for inherited components. But, I don't understand why constructor with default arguments doesn't suitable. Without any argument it should work like default constructor. Can anyone explain it? Seems I doing it wrong
Its first time when I'm using ORM.
Here is my code:
public class EwidencjonowanaRzeczMap : ClassMap<EwidencjonowanaRzecz>
{
public EwidencjonowanaRzeczMap()
{
Id(c => c.Id);
References(m => m.WagaRef);
}
}
public class WagaMap : ClassMap<Waga>
{
public WagaMap()
{
Id(m => m.Id);
Map(m => m.WagaPrzedmiotu);
HasMany(m => m.ListaRzeczy).Inverse().Cascade.All();
}
}
public class EwidencjonowanaRzecz
{
public virtual int Id { get; set; }
public virtual Waga WagaRef { get; set; }
}
public class Waga
{
public virtual int Id { get; set; }
public virtual String WagaPrzedmiotu { get; set; }
public virtual IList<EwidencjonowanaRzecz> ListaRzeczy { get; set; }
public Waga()
{
ListaRzeczy = new List<EwidencjonowanaRzecz>();
}
}
Here is my function to select records:
public IList<T> SelectAllFromTable<T>()
{
IList<T> czyDanePoprawne = null;
try
{
_sesja = NHibernateHelper.CreateSessionFactory().OpenSession();
using (_sesja.BeginTransaction())
{
czyDanePoprawne = _sesja.CreateCriteria(typeof(T)).List<T>();
}
}
catch (Exception)
{
CzyNieWystapilyBledy = true;
}
return czyDanePoprawne;
}
Tables:
ewidencjonowanarzecz [ Id - PK , IdWagi - FK (Waga.Id)]
waga [ Id - PK , WagaPrzedmiotu ]
Every type Id's - INT(3)
WagaPrzedmiotu is VARCHAR(10)
How to read to list these 2 fields?:
Id (ewidencjonowanarzecz) and WagaPrzedmiotu (waga)
In my mapping:
EwidencjaList = ModelBazy.SelectAllFromTable<EwidencjonowanaRzecz>();
is empty.
What is wrong with my mapping?
If you want to select the table record you can try this :
_sesja = NHibernateHelper.CreateSessionFactory().OpenSession();
var tabledata = _sesja.CreateQuery("from EwidencjonowanaRzecz").List<EwidencjonowanaRzecz>();
If you want to check whether if it fetches the values, try this
string value = tabledata.WagaPrzedmiotu;