I have two tables:
class company
{
prop Id //PK
prop EskaId //PK
}
class policy
{
prop Id //PK
prop CompanyEskaId //FK
}
I want to link CompanyEskaId with EskaId using Fluent API.
I've tried
HasRequired(b => b.CompanyObj).WithMany().HasForeignKey(c => c.CompanyEskaId);
But in this case how can determine the target PK is EskaId, not the Id
In EF6 an Entity has only a single Key so you would have to make EskaId the key of CompanyObj.
EF Core supports Alternate Keys where an entity has multiple keys and a Foreign Key can refer to any key. eg:
internal class MyContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasOne(p => p.Blog)
.WithMany(b => b.Posts)
.HasForeignKey(p => p.BlogUrl)
.HasPrincipalKey(b => b.Url);
}
}
Related
I'm working on a simple import tool written in C# for .NET Framework 4.7 to read a MS-Access file and save it to a PostGIS database.
I'm having trouble letting EF6 now my Point object is a NetTopology class and not another table.
When I read of write my data from my PostGIS database I get this error:
System.Data.Entity.ModelConfiguration.ModelValidationException
HResult=0x80131500
Message=One or more validation errors were detected during model generation:
Importer.Point: : EntityType 'Point' has no key defined. Define the key for this EntityType.
Points: EntityType: EntitySet 'Points' is based on type 'Point' that has no keys defined.
Here is some code:
public static void Main(string[] args)
{
// Place this at the beginning of your program to use NetTopologySuite everywhere (recommended)
NpgsqlConnection.GlobalTypeMapper.UseNetTopologySuite();
using (var context = new PostGisContext())
{
var newKey = context.MyLocations.Count();
}
}
context.MyLocations.Count() is throwing the error.
public class PostGisContext : DbContext
{
public DbSet<MyLocation> MyLocations { get; set; }
public PostGisContext() : base("name=PostgreSql")
{ }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// PostgreSQL uses the data schema by default - not dbo.
modelBuilder.HasDefaultSchema("data");
// Don't use plural table names:
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
// Change CamelCase to lower-case:
modelBuilder.Types().Configure(c => c.ToTable(LowerCaseTableName(c.ClrType)));
modelBuilder.Conventions.Add(new LowerCaseConvention());
base.OnModelCreating(modelBuilder);
}
private static string LowerCaseTableName(Type type)
{
var result = Regex.Replace(type.Name, ".[A-Z]", m => m.Value[0] + "_" + m.Value[1]);
return result.ToLower();
}
}
// A custom convention.
internal class LowerCaseConvention : IStoreModelConvention<EdmProperty>
{
public void Apply(EdmProperty property, DbModel model)
{
property.Name = Regex.Replace(property.Name, ".[A-Z]", m => m.Value[0] + "_" + m.Value[1]).ToLower();
}
}
When changing the Location column to string the above code works fine.
public class MyLocation
{
[Key]
public int Id { get; set; }
public string LocationId { get; set; }
public NetTopologySuite.Geometries.Point Location { get; set; }
}
public class NpgSqlConfiguration : DbConfiguration
{
public NpgSqlConfiguration()
{
const string name = "Npgsql";
SetProviderFactory(providerInvariantName: name,
providerFactory: NpgsqlFactory.Instance);
SetProviderServices(providerInvariantName: name,
provider: NpgsqlServices.Instance);
SetDefaultConnectionFactory(connectionFactory: new NpgsqlConnectionFactory());
}
}
I must be forgetting a setting or something, but I can't find it.
Any help will be much appreciated.
NetTopologySuite and PostGIS aren't supported in Entity Framework 6, only in Entity Framework Core. This is because EF6's type system is closed and does not allow for flexible provider-specific extensions in the way that EF Core does.
I've got a database structure where I've got an Equipment table with the columns Equipment_Id, Field_1, and Field_2. I've got an Equipment_Locale table with the fields Equipment_Id and Desc. The Ids are the same in both tables, and there is a one-to-one relationship between these tables.
I've got the following entity:
public class Equipment
{
public long Id { get; set; }
public string Description { get; set; }
public long Field1 { get; set; }
public long Field2 { get; set; }
}
I've got the following EntityTypeConfiguration:
public class EquipmentMapping : EntityTypeConfiguration<Equipment>
{
public EquipmentMapping()
{
ToTable("EQUIPMENT");
HasKey(e => e.Id);
Property(e => e.Id).HasColumnName("EQUIPMENT_ID");
Property(e => e.Field1).HasColumnName("FIELD_1");
Property(e => e.Field2).HasColumnName("FIELD_2");
// TODO: Okay, now I need to get the description in here!
}
}
I need to map the description in there, though, which comes from the EQUIPMENT_LOCALE table's DESC column.
This answer gives me a pretty clear idea on how I could use this if I was defining the mapping in ModelBuilder. However, we've been using files with EntityTypeConfigurations on this project and just having the model builder add those configurations, and I'm not sure how to set up a two table mapping in one of those. How can I accomplish this?
It turns out that the answer I linked which did it in ModelBuilder was really, really close to what I needed to simply put in my EntityTypeConfiguration file. I'd just never used Map() in EntityTypeConfiguration before so I was a bit clueless.
The following seems to work for me:
public class EquipmentMapping : EntityTypeConfiguration<Equipment>
{
public EquipmentMapping()
{
HasKey(e => e.Id);
Property(e => e.Id).HasColumnName("EQUIPMENT_ID");
Property(e => e.Field1).HasColumnName("FIELD_1");
Property(e => e.Field2).HasColumnName("FIELD_2");
Property(e => e.Description).HasColumnName("DESC");
Map(m =>
{
m.Properties(e => new
{
e.Id,
e.Field1,
e.Field2
});
m.ToTable("EQUIPMENT");
});
Map(m =>
{
m.Properties(e => new
{
e.Id,
e.Description
});
m.ToTable("EQUIPMENT_LOCALE");
});
}
}
You're going to need a navigation property in your parent:
public virtual Equipment_Locale Equipment_Locale { get; set; }
Then you can add to equipment configuration mapping like:
HasRequired(p => p.Equipment_Locale )
.WithMany()
.HasForeignKey(p => p.Equipment_LocaleId )
.WillCascadeOnDelete(false);
See here for relationship mapping: https://msdn.microsoft.com/en-us/data/jj591620.aspx
I have an entity like this:
public class Employment
{
public virtual Company Company {get; set;}
public virtual Person Person {get; set;}
public virtual string Description {get; set;}
}
providing a relationship between two other entities. They have corresponding DTO's and I want to return a result set containing all information on persons and companies. The query is performed on the Employment table and my problem is that Hibernate generates one select statement per company and person.
In my database, The Employment table has 1000 rows. Nhibernate generates 2001 select statements, one for the Employment list and one select for each Person and Company as I map them to DTO's.
I would like hibernate to fetch all information at once, in SQL I would have done something like this:
SELECT e.Description, c.A, c.B, c.C, p.D, p.E, p.F
FROM Employment e
JOIN Company c ON e.Company_Id = c.Company_Id
JOIN Person p ON e.Person_Id = p.Person_Id;
Or even
SELECT Description FROM Employment;
SELECT c.A, c.B, c.C FROM Employment e
JOIN Company c ON e.Company_Id = c.Company_Id;
SELECT p.D, p.E, p.F FROM Employment e
JOIN Person p ON e.Person_Id = p.Person_Id;
I am a pretty fresh user of nHibernate, QueryOver.I welcome Linq-To-Entities answers as well but I prefer to avoid LINQ query expressions.
I've looked all over the web, read about JoinQuery, JoinAlias and Fetch and have come as far as something like this:
//This works, but the objects are retrieved as PersonProxy and CompanyProxy,
//generating 2 SELECT statements for each Employment I map to EmploymentDto
var queryOver =
session.QueryOver<Employment>()
.Fetch(x => x.Person).Eager
.Fetch(x => x.Company).Eager
var mapResult = MappingEngine.Map<IList<EmploymentDto>>(queryOver.List());
//This works, but the objects are still retrieved as PersonProxy and CompanyProxy,
var queryOver =
session.QueryOver<Employment>()
.JoinAlias(x => x.Person, () => personAlias, JoinType.InnerJoin)
.JoinAlias(x => x.Company, () => companyAlias, JoinType.InnerJoin);
var mapResult = MappingEngine.Map<IList<EmploymentDto>>(queryOver.List());
JoinQuery provided the same result aswell. I feel I am missing something important here. Something should be done in the query or before .List() to fetch all child entities instead of loading a list with lots of Employment entities loaded with PersonProxy and CompanyProxy. I can, however, not find out how...
EDIT: Added mapping
Database tables:
TABLE Company(
Id,
A,
B,
C)
TABLE Person(
Id,
D,
E,
F);
TABLE Employment(
Person_Id,
Company_Id,
Description);
Entities
public class Company
{
public virtual string Id { get; set; }
public virtual string A { get; set; }
public virtual bool B { get; set; }
public virtual bool C { get; set; }
}
public class Person
{
public virtual string Id { get; set; }
public virtual string D { get; set; }
public virtual string E { get; set; }
public virtual string F { get; set; }
}
public class Employment
{
public virtual Person Person { get; set; }
public virtual Company Company { get; set; }
public virtual string Description { get; set; }
public override bool Equals(object obj)
{
Employment toCompare = obj as Employment;
if (toCompare == null)
return false;
return (this.GetHashCode() != toCompare.GetHashCode());
}
public override int GetHashCode()
{
unchecked
{
int results = Person != null ? Person.GetHashCode() : 0;
results = (results * 397) ^ (Company != null ? Company.GetHashCode() : 0);
results = (results * 397) ^ (Description != null ? Description.GetHashCode() : 0);
return results;
}
}
}
Mapping
public class CompanyMap : SyncableClassMap<Company>
{
public CompanyMap()
{
Table("Company");
Id(x => x.Id).Column("Id").GeneratedBy.Assigned();
Map(x => x.A).Column("A");
Map(x => x.B).Column("B").CustomType<YesNoType>();
Map(x => x.C).Column("C").CustomType<YesNoType>();
}
}
public class PersonMap : SyncableClassMap<Person>
{
public PersonMap()
{
Table("Person");
Id(x => x.Id).Column("Id").GeneratedBy.Assigned();
Map(x => x.D).Column("D");
Map(x => x.E).Column("E");
Map(x => x.F).Column("F");
}
}
public class EmploymentMap : ClassMap<Employment>
{
public EmploymentMap()
{
Table("Employment");
CompositeId()
.KeyReference(x => x.Person, "Person_Id")
.KeyReference(x => x.Company, "Company_Id");
Map(x => x.Description, "Description");
}
}
after your edit i see you have a keyreference instead of a normal many-to-one.
Unfortunatly this seems to be a limitation of QueryOver/Criteria which does not eager load keyreferences even with Fetchmode specified. However Linq to NH does not have this limitation. Change the query to
using NHibernate.Linq;
var results = session.Query<Employment>()
.Fetch(x => x.Person)
.Fetch(x => x.Company)
.ToList();
I have experienced the same problem you're describing here. I'll take your last code snippet as an example, since that's the way I've made it work:
//This works, but the objects are still retrieved as PersonProxy and CompanyProxy,
var queryOver =
session.QueryOver<Employment>()
.JoinAlias(x => x.Person, () => personAlias, JoinType.InnerJoin)
.JoinAlias(x => x.Company, () => companyAlias, JoinType.InnerJoin);
var mapResult = MappingEngine.Map<IList<EmploymentDto>>(queryOver.List());
First of all, you shouldn't have to specify JoinType.InnerJoin since that's the default join type. Same as with you, I've also found that the persons and companies are loaded lazily this way.
However, if you change the join type to JoinType.LeftOuterJoin, you'll see that everything is loaded eagerly. At least that's what I've experienced. So try change your code to the following:
//This works, but the objects are still retrieved as PersonProxy and CompanyProxy,
var queryOver =
session.QueryOver<Employment>()
.JoinAlias(x => x.Person, () => personAlias, JoinType.LeftOuterJoin)
.JoinAlias(x => x.Company, () => companyAlias, JoinType.LeftOuterJoin);
var mapResult = MappingEngine.Map<IList<EmploymentDto>>(queryOver.List());
I can't explain to you why it is this way, I've just found this to work out of own experiences. And if it's problematic for you doing a left outer join, you can instead try to filter accordingly in your code before (or while) doing the mapping.
I have two data models Blog and Post. BlogId is a foreign key on the Post table
public class Blog
{
public int ID { get; set; }
public string Title { get; set; }
public virtual ICollection<Post> Posts { get; set; }
...
}
public class Post
{
public int ID { get; set; }
public virtual int BlogId { get; set; }
public string Title { get; set; }
...
}
Now this works fine and my Repository is happy and pulls everything as expected from DB.
My question is - Is there a way to limit the number of Posts that get retrieved. Perhaps some LINQ magic?
Here is what my current method in the repository looks like:
public Business FindBlog(int id)
{
return this.context.Get<Blog>().SingleOrDefault(x => x.ID == id);
}
Unfortunatelly EFv4 doesn't offer easy way to limit number of returned record for navigation property.
If you are using EntityObject derived entities you can use something like:
var blog = context.Blogs
.Single(b => b.Id == blogId);
var posts = blog.Posts
.CreateSourceQuery()
.OrderByDescending(p => p.Date)
.Take(numberOfRecords)
.ToList();
If you are using POCOs you must execute separate query for that (in case of proxied POCOs you can convert navigation property to EntityCollection<Post> to get access to CreateSourceQuery):
var blog = context.Blogs
.Single(b => b.Id == blogId);
var posts = context.Posts
.Where(p => p.BlogId == blogId)
.OrderByDescending(p => p.Date)
.Take(numberOfPosts)
.ToList();
EFv4.1 and DbContext API offers the way to load only limited number of related entities:
var blog = context.Blogs
.Single(b => b.Id == blogId);
context.Entry(blog)
.Collection(b => b.Posts)
.Query()
.OrderByDescending(p => p.Date)
.Take(numberOfPosts)
.Load();
Edit:
You can do it in single query with projection:
var blog = context.Blogs
.Where(b => b.Id == blogId)
.Select(b => new
{
Blog = b,
Posts = b.Posts
.OrderByDescending(p => Date)
.Take(numberOfRecords)
})
.SingleOrDefault()
Just be aware that you must access posts from second paremeter of anonymous type.
Do you mean:
public List<Post> GetBlogPosts(Blog blog, int numberOfPosts)
{
return blog.Posts.Take(numberOfPosts);
}
I am using EF4 CTP5 to try to persist a POCO object that is split among two tables, the link being the ContactID. When i save a contact, i want the core contact information saved in one table (Contacts), and the link to the user who owns the contact saved in another (UserToContacts). I have the custom mapping defined below, but when I SaveChanges, i get the following error:
A value shared across entities or associations is generated in more than one location. Check that mapping does not split an EntityKey to multiple store-generated columns.
Any ideas would be greatly appreciated!
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
/// Perform Custom Mapping
modelBuilder.Entity<Contact>()
.Map(mc =>
{
mc.Properties(p => new
{
p.ContactID,
p.FirstName,
p.MiddleName,
p.LastName
});
mc.ToTable("Contacts");
})
.Map(mc =>
{
mc.Properties(p => new
{
p.ContactID,
p.UserID
});
mc.ToTable("UserToContacts");
});
}
This is a bug that EF team have fixed it in their code base after CTP5 was released. Entity splitting should only result in identity being used on one of the tables but CTP5 configures it for all tables (if you look into your tables you'll see that ContactID is configured as an identity column in both).
The workaround for now is to not using identity with table splitting at all:
public class Contact
{
[DatabaseGenerated(DatabaseGenerationOption.None)]
public int ContactID { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public int UserID { get; set; }
}
Which means you are responsible to provide valid PKs when creating a new Contact object.
Just don't specify the ID, it will be added automatically
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
/// Perform Custom Mapping
modelBuilder.Entity<Contact>()
.Map(mc =>
{
mc.Properties(p => new
{
p.FirstName,
p.MiddleName,
p.LastName
});
mc.ToTable("Contacts");
})
.Map(mc =>
{
mc.Properties(p => new
{
p.UserID
});
mc.ToTable("UserToContacts");
});
}