I've looked at some other answers and lots of articles but this simple part eludes me still. I'm using EF code first 4.1 but am happy to move to a newer version if it makes it easier. I have a main fact table like so:
namespace Spend.Models
{
public class ExpenseItem
{
[Key]
public String UniqueID_ERLineID { get; set; }
public String ERNum { get; set; }
public String ItemNum { get; set; }
public String Parent_Expense_Item { get; set; }
public String Card_Number { get; set; }
...
and several tables hanging off that which have many to one relationships with the ExpenseItems:
public class ExpenseItemAccounting
{
[Key]
public String UniqueID_Accounting { get; set; }
public String ERLineID { get; set; }
public String ERNum { get; set; }
public String ItemNum { get; set; }
As we can see, the ERLineID in the second table joins to UniqueID_ErLineID in the first, so the 'conventions' I normally rely on don't work. So I need to use a virtual ICollection - but I'd like it to specify these fields as the link. Any assistance on how to do this appreciated.
PS. I can't rename the DB fields at present.
@LUKE:
I applied the changes you mention, they make sense. I get the following error however:
System.Data.Entity.ModelConfiguration.ModelValidationException occurred
Message=One or more validation errors were detected during model generation:
System.Data.Edm.EdmAssociationType: : Multiplicity conflicts with the referential constraint in Role 'ExpenseItemAccounting_ExpenseItem_Target' in relationship 'ExpenseItemAccounting_ExpenseItem'. Because all of the properties in the Dependent Role are non-nullable, multiplicity of the Principal Role must be '1'.
System.Data.Edm.EdmAssociationEnd: : Multiplicity is not valid in Role 'ExpenseItemAccounting_ExpenseItem_Source' in relationship 'ExpenseItemAccounting_ExpenseItem'. Because the Dependent Role refers to the key properties, the upper bound of the multiplicity of the Dependent Role must be �1�.
Source=EntityFramework
StackTrace:
at System.Data.Entity.ModelConfiguration.Edm.EdmModelExtensions.ValidateAndSerializeCsdl(EdmModel model, XmlWriter writer)
at System.Data.Entity.DbModelBuilder.Build(DbProviderManifest providerManifest, DbProviderInfo providerInfo)
at System.Data.Entity.DbModelBuilder.Build(DbConnection providerConnection)
at System.Data.Entity.Internal.LazyInternalContext.CreateModel(LazyInternalContext internalContext)
at System.Data.Entity.Internal.RetryLazy`2.GetValue(TInput input)
at System.Data.Entity.Internal.LazyInternalContext.InitializeContext()
at System.Data.Entity.Internal.InternalContext.GetEntitySetAndBaseTypeForType(Type entityType)
at System.Data.Entity.Internal.Linq.InternalSet`1.Initialize()
at System.Data.Entity.Internal.Linq.InternalSet`1.get_InternalContext()
at System.Data.Entity.Infrastructure.DbQuery`1.System.Linq.IQueryable.get_Provider()
at System.Linq.Queryable.SelectMany[TSource,TCollection,TResult](IQueryable`1 source, Expression`1 collectionSelector, Expression`1 resultSelector)
at EmailClient.Prog.getData() in C:\MF\Dropbox\Dev_LN_Projects\04_QA\EmailClient\EmailClient\Prog.cs:line 172
InnerException:
This came when I tried the following linq query:
var geee = (from e in db.ExpenseItems
from f in db.ExpenseItemFbtItems
where
e.Item_Transaction_Date.Value.Year == 2011 &&
e.Item_Transaction_Date.Value.Month == 8
select new { A = e.UniqueID_ERLineID, B = f.ERLineID.First() });
I actually expected to be able to say e.ExpenseItemAccounting.ItemNum or something like that - do I need to put something in the ExpenseItem definition to achieve this?
My model is setup with the following. The base.OnModelCreating appeared via intellisense and I've tried it with/without it for the same result:
public class SpendDB : DbContext
{
public DbSet<ExpenseAttachment> ExpenseAttachments {get; set; }
public DbSet<ExpenseComment> ExpenseComments {get; set; }
public DbSet<ExpenseItemAccounting> ExpenseAccountings {get; set; }
public DbSet<ExpenseItemFbtItem> ExpenseItemFbtItems {get; set; }
public DbSet<ExpenseItem> ExpenseItems {get; set; }
public DbSet<ExpenseItemViolation> ExpenseItemViolations {get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ExpenseItemAccounting>().HasOptional(e => e.ExpenseItem).WithMany().HasForeignKey(e => e.UniqueID_Accounting);
}
}
Perhaps I need to put the virtual ICollection in the ExpenseItem definition? Or is it perhaps the other way around - say modelBuilder.Entity has optional ExpenseItemAccounting? That sounds more intuitive to me but I'm (clearly) not very good at this so take it with a grain of salt!
Thanks again
Do this:
public class ExpenseItemAccounting
{
[Key]
public String UniqueID_Accounting { get; set; }
public ExpenseItem ExpenseItem{get;set;}
public String ERLineID { get; set; }
public String ERNum { get; set; }
public String ItemNum { get; set; }
}
Then in your modelBuilder use
modelBuilder.Entity<ExpenseItemAccounting>()
.HasOptional(e => e.ExpenseItem).WithMany()
.HasForeignKey(e => e.UniqueID_Accounting );
EDIT:
To configure the navigation property to have a collection on the other end you can simply add it like this
public class ExpenseItem
{
[Key]
public String UniqueID_ERLineID { get; set; }
public String ERNum { get; set; }
public String ItemNum { get; set; }
public String Parent_Expense_Item { get; set; }
public String Card_Number { get; set; }
public ICollection<ExpenseItemAccounting> ExpenseItemAccountings{ get; set; }
}
then wire it up by modifying the model builder config as follows:
modelBuilder.Entity<ExpenseItemAccounting>()
.HasOptional(e => e.ExpenseItem).WithMany(e=> e.ExpenseItems)
.HasForeignKey(e => e.UniqueID_Accounting );
This will wire it up so that the ExpenseItem will have a list of all child ExpensItemAccounting's, you could also add a singular version of this if that makes more sense such as:
public class ExpenseItem
{
[Key]
public String UniqueID_ERLineID { get; set; }
public String ERNum { get; set; }
public String ItemNum { get; set; }
public String Card_Number { get; set; }
public ExpenseItemAccounting Parent_Expense_Item { get; set; }
}
and using the modelBuilder for config:
modelBuilder.Entity<ExpenseItemAccounting>()
.HasOptional(e => e.ExpenseItem)
.WithOptionalDependent(e=>e.Parent_Expense_Item);
i think if you also want the FK references wired up (not just the navigation properties) you need to do this in a seperate statement but thats a bit more fiddly.
Take a look at the MSDN page on navigation properties and how to use the modelBuilder as its got lots of good examples of advanced stuff.
http://msdn.microsoft.com/en-us/library/hh295843(v=vs.103).aspx