What is the best way to set navigation property having only id in Entity Framework?
Asked Answered
M

5

9

I have the next enitities:

public class MyEntity
{
    public virtual int Id { get; set; }
    public virtual MySecondEntity SecondEntity { get; set; }
}
public class MySecondEntity
{
    public virtual int Id { get; set; }
    ...
    some properties here
    ...
}

I don't want to create MySecondEntityID property in order to have clean model.

I need to set myEntity.MySecondEntity by its Id, but I don't want to request MySecondEntity from DB.

Is it possible to do it without creating MyEntity.MySecondEntityID property and without loading the whole MySecondEntity object or even without any db requests?

Muddleheaded answered 5/2, 2014 at 16:34 Comment(4)
You want a MyEntity object without an initialized MySecondEntity?Aleut
What version of Entity Framework are you using?Gerontocracy
I use the latest version of EF.Muddleheaded
Loetn, I know that I can use Include(..) or something like this to init the property, but the question is: hot to set it by ID without request MySecondEntity from DB?Muddleheaded
K
9

It is possible to create the MySecondEntity object manually, and attach it to the context as an unchanged object.

var secondEntity = new MySecondEntity();
secondEntity.Id = id;
context.MySecondEntities.Attach(secondEntity);
entity.SecondEntity = secondEntity;

To keep it simple, I have ignored the possibility that a MySecondEntity object with the same key already exists in the context. To make that work, you should check the local entities (for example by searching context.MySecondEntities.Local) and re-use an entity if you find it in there.

Note: EF won't have any way of knowing that the other properties of secondEntity, which are left at their default values, don't correspond to what's in the database. For what you're doing now, that doesn't matter, but it may matter at a later stage.

Known answered 6/2, 2014 at 8:50 Comment(4)
that's so hacky. Why not just make the SecondEntityId nullable?Erastatus
@Erastatus The OP here doesn't want to have a SecondEntityId property at all, but even if the OP did want that, how would making it nullable help? Setting SecondEntityId should work with non-nullable FK properties just as well as with nullable ones.Known
I thought that with a nullable property, you don't need to set it at all?Erastatus
@Erastatus That's not really wrong, but it shows that we're talking about two totally different things. Take another look at the question, and try to understand what the OP is after. If you then still feel that there is a simpler way to achieve that, you're more than welcome to post it as an answer.Known
T
3

For entity Framework 5 or later you can add an Id next to a navigational property, which will be auto-populated when the entity is loaded from the db.

public class MyEntity 
{
    public int Id { get; set;} 

    public int MySecondEntityId { get; set;} 
    public virtual MySecondEntity MySecondEntity { get; set;} 
} 

Setting MySecondEntityId is enough to create the relationship (after saving). This way you don't have to load the actual entity from db.

If you have a nullable foreign key, the making the navigational Id nullable is enough.

public int? MySecondEntityId { get; set;} 
Translocation answered 5/2, 2014 at 18:20 Comment(5)
But I don't want to have MySecondEntityId in my model! :)Muddleheaded
@AlexanderVasilyev: You can't not to have. However you can make it internal or private so it won't be exposed outside.Bucktooth
@abatishchev: Why I cannot not to have? Today I have a model without ForeignID properties. The only problem I stuck is setting it by Id. I have to load foreign object from DB and then set. But at the same time it is an advantage because I can theck existence of foreign object in DB before exception on SaveChanges() and answer with appropriate message to a client.Muddleheaded
You can still perform that check when you feel that is necessary, but at least in my own code I come across a lot of situations where I have a known Id and there is no need for an extra trip to the db. Adding the foreign key gives you more flexibility, whether you add it as a public property or a private as suggest by abatishchev.Translocation
A consideration here is that supplying a raw Id is error-prone when that record doesn't exist, and so I kinda feel like (dependent on the situation) that the call to the DB to check if it exists rules out the supposed advantage of not having to retrieve a user object. That, or simply letting the key ref exception happen and catching it.Ardeb
S
2

Use a mutator method to modify a protected property for the ID:

public class MyEntity
{
    public virtual int Id { get; set; }
    public virtual MySecondEntity SecondEntity { get; set; }

    [ForeignKey( "SecondEntity" )]
    protected virtual int? MySecondEntityId { get; set; }

    public void SetSecondEntityId( int? id )
    {
        MySecondEntityId = id;
    }
}
Succory answered 5/2, 2014 at 18:27 Comment(0)
A
0

If you want to get the MyEntity object from your database without initializing the MySecondEntity object, you can turn off lazy loading.

Method one

Turn of lazy loading on your context.

Method two

Remove the virtual keyword from your entity:

public class MyEntity
{
    public virtual int Id { get; set; }
    public MySecondEntity SecondEntity { get; set; }
}

This will disable lazy loading for SecondEntity. More info.

Aleut answered 5/2, 2014 at 16:52 Comment(0)
V
0

Finally found a solution for this problem of keeping our models as clean as possible: Shadow foreign key.

I had a bit more complex case and got it working.

Model


// main class that is aware of the other
public sealed class Foo
{
    public int Id { get; set; }

    public IEnumerable<FooBar> FooBars { get; set; }

    // ... other props
}

// class that associates Foos and Bars with some extra props (not tied to Foos or Bars specifically). Goal here is to not have any ID props
public sealed class FooBar
{
    public Foo Foo { get; set; }

    public Bar Bar { get; set; }

    public bool Enabled { get; set; }
}

// Bar doesn't even know Foo exists
public sealed class Bar
{
    public int Id { get; set; }

    // ... other props
}

In my case I have a many to many relationship, so the primary key of FooBar is a composite key of the two main table IDs.

OnModelCreating:

modelBuilder.Entity<FooBar>(b =>
{
    const string FooId = "FooId";
    const string BarId = "BarId";
    b.Property<int>(FooId);
    b.Property<int>(BarId);

    b.HasKey(FooId, BarId);
});

Conventions that care of the rest.

Vair answered 30/1, 2023 at 13:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.