Dear fellow programmers,
I'm stuck on this basic concept within EF and can't find any solution on stackoverflow.
I want to have One-to-One optional relation between: FluxLocation and Address.
(Normal words: a flux location could be provided with a physical address)
Note the database is already present and final.
SQL TABLES:
CREATE TABLE sales.sales_flux_location(
id serial PRIMARY KEY,
-- Many unusefull properties
sales_address_id integer REFERENCES sales_address
);
CREATE TABLE sales.sales_address(
id serial PRIMARY KEY,
-- Many unusefull properties
);
EF Mapping:
public partial class FluxLocation
{
public int Id { get; set; }
//Many unusefull properties.
[ForeignKey("Address")]
public int? AddressId { get; set; }
public Address Address { get; set; }
}
internal partial class FluxLocationConfiguration : EntityTypeConfiguration<FluxLocation>
{
public FluxLocationConfiguration()
{
//PK
HasKey(x => x.Id);
ToTable("sales_flux_location", "sales");
Property(a => a.Id)
.HasColumnName("id")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
//FK
HasOptional(l => l.Address)
.WithOptionalDependent(a => a.FluxLocation);
Property(l => l.AddressId)
.HasColumnName("sales_address_id")
.IsOptional();
// + mapping other properties.
}
public partial class Address
{
public int Id { get; set; }
// other properties
public FluxLocation FluxLocation { get; set; }
}
internal partial class AddressConfiguration : EntityTypeConfiguration<Address>
{
public AddressConfiguration()
{
//PK
HasKey(a => a.Id);
ToTable("sales_address", "sales");
Property(a => a.Id)
.HasColumnName("id")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
//FK
HasOptional(a => a.FluxLocation).WithOptionalPrincipal(l=>l.Address);
// mapping many unusefull properties
}
TEST CASE:
var dbAddress = Context.AddressSet.Add(new Address {Country = "BEL", CityName="Brussel", Street = Guid.NewGuid().ToString() });
var dbLocation = Context.FluxLocationSet.Add(new FluxLocation { AddressId = dbAddress.Id, Country = "BEL", Type = "MARKET", ExtId = Guid.NewGuid().ToString() });
Context.SaveChanges();
Error on Context.SaveChanges():
"42703: column \"Address_Id\" of relation \"sales_flux_location\" does not exist"}
Which is correct because the column name is "sales_address_id".
If any one could help why he is ignoring the propery columnname mapping?
I'm happy to provide more code if needed.
EF is not picking up that you want sales_address_id as the FK so it tried to create Address_Id. Also, there is some weirdness in how EF does 0:1 - essentially you need to fool it with a 1:M
So try this:
//FK
HasOptional(l => l.Address)
.WithMany()
.HasForeignKey(d => d.AddressId);
Link
Related
I have two entities:
public class ServiceEvent
{
public long Id { get; set; }
**public virtual Customer CustomerRef { get; set; }**
public long ServiceId { get; set; }
public string Test { get; set; }
}
public class Customer
{
public long Id { get; set; }
**public virtual ServiceEvent Event { get; set; }**
public string TestCustomer { get; set; }
public long CustonerId { get; set; }
}
and mappings:
public ServiceEventMap()
{
Id(x => x.Id, m => m.Generator(Generators.Native));
Property(x => x.ServiceId, m => {m.Unique(true); m.NotNullable(true);});
Property(x => x.Test);
OneToOne(x => x.CustomerRef, m => m.PropertyReference(typeof(Customer).GetPropertyOrFieldMatchingName("Event")));
}
public CustomerMap()
{
Id(x => x.Id, m => m.Generator(Generators.Native));
Property(x => x.CustonerId);
Property(x => x.TestCustomer);
ManyToOne(x => x.Event, m =>
{
m.PropertyRef("ServiceId");
m.Column(mc =>
{
mc.Name("service_id");
mc.NotNullable(true);
});
m.ForeignKey("fk_service_event_customer");
});
}
I have used an example from NOtherDev. As is it described in example, I have made many-to-one relation on one side and "virtual" one-to-one relation on the other side.
It works fine in case when primary keys have the same values in both tables. But I need this relation to be based on ServiceId field, not primary key.
Currently I have a problem with selection of the data. All ServiceEvent objects that are read from database has null in CustomerRef property.
I have following data in database:
customer table
id |service_id |test_customer |custoner_id
7 |55 |test string |444534543
service_event table
id |service_id |test
2 |55 |another string
When I'm reading Customer it has proper link to ServiceEvent object.
It seems that PropertyReference setting in one-to-one mapping simply doesn't work.
Please help me to find out where is my mistake.
One to one relations always share the primary key, if that wasn't the case, you could have several entities related to the same. In one of the entities (the dependent), it is both the primary and the foreign key, and in the other (the primary) it is just the primary key.
I want to map a class that has a property of type ICollection<> using NHibernate mapping by code. The code below works. But I don't like the extra Person property within CarSet to make the mapping work.
public class PersonSet
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<CarSet> Cars { get; set; }
}
public class CarSet
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual PersonSet Person { get; set; }
}
public class PersonSetMap : ClassMapping<PersonSet>
{
public PersonSetMap()
{
Id(x => x.Id, m=>m.Generator(Generators.Identity));
Property(x=>x.Name);
Set(x => x.Cars, c =>
{
c.Key(k =>
{
k.Column("PersonId");
});
c.Cascade(Cascade.Persist);
c.Lazy(CollectionLazy.NoLazy);
}, r =>
{
r.OneToMany();
}
);
}
}
public class CarSetMap : ClassMapping<CarSet>
{
public CarSetMap()
{
Id(x => x.Id, m => m.Generator(Generators.Identity));
Property(x => x.Name);
ManyToOne(x => x.Person, m =>
{
m.Column("PersonId");
m.Cascade(Cascade.None);
m.NotNullable(true);
});
}
}
public void Save(){
using (var session = Cfg.Session)
using (var tx = session.BeginTransaction())
{
PersonSet John = new PersonSet { Name = PersonName.John };
John.Cars = new List<CarSet> {
new CarSet { Name = CarnName.BMW,Person = John},
new CarSet { Name = CarnName.BM,Person = John }};
session.Save(entity);
tx.Commit();
}
}
The code above generates SQL script below:
create table PersonSet (
Id INT IDENTITY NOT NULL,
Name NVARCHAR(255) null,
primary key (Id)
)
create table CarSet (
Id INT IDENTITY NOT NULL,
Name NVARCHAR(255) null,
PersonId INT not null,
primary key (id)
)
alter table CarSet
add constraint FKF967D6489A220265
foreign key (PersonId)
references PersonSet
What I want is to generate SQL script with difference shown below, and keep the rest the same:
create table CarSet (
Name NVARCHAR(255) null,
PersonId INT not null,
)
Ideally I want the CarSet like this instead:
public class CarSet
{
public virtual int PersonId { get; set; }
public virtual string Name { get; set; }
}
Any idea?
map Cars as ComponentCollection
class Person
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Car> Cars { get; set; }
}
class Car
{
public virtual Person Owner { get; set; }
public virtual string Name { get; set; }
}
public class PersonMap : ClassMapping<Person>
{
public PersonMap()
{
Id(x => x.Id, m => m.Generator(Generators.Identity));
Property(x => x.Name);
Set(x => x.Cars, c =>
{
c.Key(k => k.Column("PersonId"));
c.Cascade(NHibernate.Mapping.ByCode.Cascade.Persist);
c.Lazy(CollectionLazy.NoLazy);
}, r =>
{
r.Component(c =>
{
c.Parent(x => x.Owner);
c.Property(x => x.Name);
});
});
}
}
Your ideal solution isn't possible. To use a CarSet table without it's own ID column it has to be a component, but component sets can't have nullable columns. If it's ok for you to mark Name as not-null you can adapt the solution Firo posted.
If that's not ok you can at least solve your first request to remove the Person property. Just delete the property and mark the key column in your set mapping as not-nullable. CarSet will still be an entity (and therefore have it's own ID) but you don't need the reference to PersonSet in code.
Btw, why are your classes postfixed by Set? Just naming them Person and Car would be much better since they only represent one person or car, not a collection of them.
I have a class referencing a (template)text which has for each tenant and each use case several possible texts
class Class1
{
[...]
public virtual Text TitleName { get; set; }
}
class Text
{
public virtual int TenantId { get; set; }
public virtual string Key { get; set; }
public virtual int Number { get; set; }
public virtual string Value { get; set; }
}
Unfortunatly the tablestructure looks like
Table Class1
...
textnumber int,
Table Text
tenant int,
key varchar (10),
number int,
pkey(tenant, key, number);
because Class1 always refers to tenant = 0 (all tenants) and key = "class1text"
Edit:
i need .Where() but References() doenst have it only HasMany()
What i have so far:
public void TextMap : ClassMap<Text>
{
public TextMap()
{
Table("restexts");
CompositeId()
.KeyProperty(t => t.TenantId, "tenant")
.KeyProperty(t => t.Key, "name")
.KeyProperty(t => t.Number, "number");
Map(t => t.Value, "content");
}
}
public void Class1Map : ClassMap<Class1>
{
public TextMap()
{
// mapping rest
References(c => c.TitleName)
.Columns("textnumber", ??, ??); // column 2 and 3 missing, because always the same
}
}
Any Ideas?
you can map your classes in this way with FluentNHibernate, it's a workaround to map only one field in OneToMany relationship.
public Class1()
{
Table("TableName");
Id(x => x.MyId).Column("TableId");
.
.
.
HasMany<TextModel>(x => x.textnumber).KeyColumn("TableField");
}
public Text()
{
Table("TableName");
Id(x => x.MyId).Column("TableId");
.
.
.
References<Class1Model>(x => x.number,"TableColumn");
}
Then you can add in your query the filters on the other fields (tenent = 0 and key = "class1text")
I hope it's helpful
running out of time i mapped it like Map(c => c.TitleNameId, "textnumber"); and have to remember the tenant and key name everytime i need the titlename (which is using magic values :(
I have three tables:
Person (Id, FirstName)
Organization (Id, Name)
PersonOrganization (PersonId, OrganizationId, Details) many-to-many table
When I first mapped this using Fluent NHibernate I did not have a details column in the PersonOrganization table and mapped this using HasManyToMany in the PersonMap and OrganizationMap (no need to create a PersonOrganization domain object or map). I could writethe following code:
Organization org = new Organization { Name = "org" };
People people = new People { FirstName = "firstname", Organization = org };
peopleRepository.Add(people); // ISession.Save(people)
unitOfWork.Commit(); // ITransaction.Commit()
NHhibernate happily committed the data to all three tables.
The issue comes up when I added the details column in the PersonOrganization table. After some research it turns out that I now have to create a new PersonOrganization domain object and map and setup a HasMany relationship for both Person and Organization. My updated model and maps below:
public class People
{
public People()
{
LinkToOrganization = new List<PeopleOrganization>();
}
public virtual int Id { get; set; }
public virtual string FirstName { get; set; }
public virtual IList<PeopleOrganization> LinkToOrganization { get; set; }
}
public class Organization
{
public Organization()
{
LinkToPeople = new List<PeopleOrganization>();
}
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<PeopleOrganization> LinkToPeople { get; set; }
}
public class PeopleOrganization
{
public virtual People People { get; set; }
public virtual Organization Organization { get; set; }
public virtual string Details { get; set; }
}
public class PeopleMap : ClassMap<People>
{
public PeopleMap()
{
Id(p => p.Id);
Map(p => p.FirstName).Length(100);
HasMany(x => x.LinkToOrganization)
.Table("PeopleOrganization")
.KeyColumn("PeopleId")
.AsBag()
.Inverse();
}
}
public class OrganizationMap : ClassMap<Organization>
{
public OrganizationMap()
{
Id(p => p.Id);
Map(p => p.Name).Length(50);
HasMany(x => x.LinkToPeople)
.Table("PeopleOrganization")
.KeyColumn("OrganizationId")
.AsBag()
.Inverse();
}
}
public class PeopleOrganizationMap : ClassMap<PeopleOrganization>
{
public PeopleOrganizationMap()
{
CompositeId()
.KeyReference(p => p.People, "PeopleId")
.KeyReference(p => p.Organization, "OrganizationId");
Map(p => p.Details).Length(100);
}
}
I now have to write the following code:
People people = new People { FirstName = "firstname" };
Organization org = new Organization { Name = "org" };
PeopleOrganization po = new PeopleOrganization { People = people, Organization = org, Details = "details" };
peopleRepository.Add(people); // ITransaction.Begin() ISession.Save(people)
organizationRepository.Add(org); // ISession.Save(org)
peopleOrganizationRepository.Add(po); // ISession.Save(po)
unitOfWork.Commit(); // ITransaction.Commit()
My questions are:
Are my mappings correctly setup to support this kind of many-to-many scenario?
Is there are way for me to just be able to do the following (which would write to all three tables):
-
People people = new People { FirstName = "firstname" };
Organization org = new Organization { Name = "org" };
PeopleOrganization po = new PeopleOrganization { People = people, Organization = org, Details = "details" };
peopleRepository.Add(people); // Just ONE ISession.Save(people)
unitOfWork.Commit(); // ITransaction.Commit()
Any input is highly appreciated.
Thanks
Firstly, you'll need to add your newly created PeopleOrganization to the collection on both entities (Organization and People). Then if you add a Cascade.All() to your HasMany chain, the saves should propagate (cascade) down to the PeopleOrganization and subsequently to Organization.
As a purely semantic suggestion, I'd recommend encapsulating the PeopleOrganization creation into a method on Person. Something like this:
class Person
{
public void AddOrganization(Organization org, string details)
{
var link = new PeopleOrganization { ... };
LinkToOrganization.Add(link)
org.LinkToPeople.Add(link);
}
}
That way you never have to deal with creating the intrim entity yourself.
I have a database schema where the convention for a foreign key's name is:
ForeignTable.Name + ForeignTable.PrimaryKeyName
So, for a Child table referencing a Parent table with a primary key column named Key, the foreign key will look like ParentKey.
Is there a way to create this convention in my Fluent NHibernate mapping?
Currently I'm using a ForeignKeyConvention implementation like this:
public class ForeignKeyNamingConvention : ForeignKeyConvention
{
protected override string GetKeyName(PropertyInfo property, Type type)
{
if (property == null)
{
// Relationship is many-to-many, one-to-many or join.
if (type == null)
throw new ArgumentNullException("type");
return type.Name + "ID";
}
// Relationship is many-to-one.
return property.Name + "ID";
}
}
This works exactly as I want for all types which have "ID" as a primary key. What I would like to do is replace the constant "ID" with the name of the primary key of the type being referenced.
If this isn't currently possible with Fluent NHibernate, I'm happy to accept that answer.
Take a look at conventions and especially at implementing a custom foreign key convention.
UPDATE:
Here's an example. Assuming the following domain:
public class Parent
{
public virtual int Id { get; set; }
}
public class Child
{
public virtual string Id { get; set; }
public virtual Parent Parent { get; set; }
}
which needs to be mapped to this schema:
create table Child(
Id integer primary key,
ParentId integer
)
create table Parent(
Id integer primary key
)
you could use this convention:
public class CustomForeignKeyConvention : IReferenceConvention
{
public void Apply(IManyToOneInstance instance)
{
instance.Column(instance.Class.Name + "Id");
}
}
and to create the session factory:
var sf = Fluently
.Configure()
.Database(
SQLiteConfiguration.Standard.UsingFile("data.db3").ShowSql()
)
.Mappings(
m => m.AutoMappings.Add(AutoMap
.AssemblyOf<Parent>()
.Where(t => t.Namespace == "Entities")
.Conventions.Add<CustomForeignKeyConvention>()
)
)
.BuildSessionFactory();
If you can get the Mapping<T> for a class, you can get the name of its Id column.
public class MyForeignKeyConvention: ForeignKeyConvention
{
public static IList<IMappingProvider> Mappings = new List<IMappingProvider>();
protected override string GetKeyName( System.Reflection.PropertyInfo property, Type type )
{
var pk = "Id";
var model = new PersistenceModel();
foreach( var map in Mappings ) {
model.Add( map );
}
try {
var mymodel = (IdMapping) model.BuildMappings()
.First( x => x.Classes.FirstOrDefault( c => c.Type == type ) != null )
.Classes.First().Id;
Func<IdMapping, string> getname = x => x.Columns.First().Name;
pk = getname( mymodel );
} catch {
}
if (property == null) {
return type.Name + pk;
}
return type.Name + property.Name;
}
}
We can get the Mapping object with a little bit of plumbing.
The constructors of ClassMap<T> can pass this into our collection of Mappers.
For AutoMapping<T>, we can use Override as follows.
.Mappings( m => m.AutoMappings.Add( AutoMap.AssemblyOf<FOO>()
.Override<User>( u => {
u.Id( x => x.Id ).Column( "UID" );
MyForeignKeyConvention.Mappings.Add( u );
}
)
For a system wide convention I believe this would serve the purpose best.
( I wasn't sure whether to include the whole text or just a portion here, since I answered it here already)
Here's the solution with links to current Fluent NHibernate & automapping documentation.
The issue (a simple example):
Say you have the simple example (from fluent's wiki) with an Entity and it's Value Objects in a List:
public class Product
{
public virtual int Id { get; set; }
//..
public virtual Shelf { get; set; }
}
public class Shelf
{
public virtual int Id { get; set; }
public virtual IList<Product> Products { get; set; }
public Shelf()
{
Products = new List<Product>();
}
}
With tables which have e.g.
Shelf
id int identity
Product
id int identity
shelfid int
And a foreign key for shelfid -> Shelf.Id
You would get the error:
invalid column name ... shelf_id
Solution:
Add a convention, it can be system wide, or more restricted.
ForeignKey.EndsWith("Id")
Code example:
var cfg = new StoreConfiguration();
var sessionFactory = Fluently.Configure()
.Database(/* database config */)
.Mappings(m =>
m.AutoMappings.Add(
AutoMap.AssemblyOf<Product>(cfg)
.Conventions.Setup(c =>
{
c.Add(ForeignKey.EndsWith("Id"));
}
)
.BuildSessionFactory();
Now it will automap the ShelfId column to the Shelf property in Product.
More info
Wiki for Automapping
Table.Is(x => x.EntityType.Name + "Table")
PrimaryKey.Name.Is(x => "ID")
AutoImport.Never()
DefaultAccess.Field()
DefaultCascade.All()
DefaultLazy.Always()
DynamicInsert.AlwaysTrue()
DynamicUpdate.AlwaysTrue()
OptimisticLock.Is(x => x.Dirty())
Cache.Is(x => x.AsReadOnly())
ForeignKey.EndsWith("ID")
See more about Fluent NHibernate automapping conventions