Home > Back-end >  How am I able to use a generic Navigation property in a Entity Framework Model?
How am I able to use a generic Navigation property in a Entity Framework Model?

Time:10-22

I am creating 2 poco types during runtime (using reflection). These two should reference each other. Later instances of these pocos can be stored in the database using entity framework.

Currently I am faced by two problems:

  1. It's not possible or at least I don't know how I am able to code this two-way referencing (because while whone Poco is described, the type of the other poco doesen't exist).

2.As I've found no answer to problem 1, I've decided to use object as the type of the references. So the Models now contain the following line:

$ public object Poco1 {get; set;} And:

public object Poco2 {get; set;}

The usage of object confronts me now with another problem. Because, during the OnModelCreating an exception is thrown, that object needs to contain an id.

As much as I understand, this means, that ef core thinks, that "object" would be the type of the model, that should be referenced.

Does anybody have an idea on how I can do what I want?

Thanks :)

CodePudding user response:

Whenever I hit a problem involving generics and reflection. I find it easier to start by solving the generic part of the problem, in a generic method;

public class Parent<TChild> where TChild : class
{
    public ICollection<TChild> Children { get; set; }
}
public class Child<TParent> where TParent : class
{
    public TParent Parent { get; set; }
}

public void DefineRelationship<TParent, TChild>(ModelBuilder modelBuilder) 
    where TChild : Child<TParent>
    where TParent : Parent<TChild>
{
    modelBuilder.Entity<TParent>()
        .HasMany(p => p.Children)
        .WithOne(c => c.Parent);
}

Now you need to use reflection to invoke the method with the correct types.

CodePudding user response:

Your entities should look something like this:

public class Poco1
{
  public int Id {get; private set;}
  ...
  public Poco2 Poco2 {get; private set;}
  
  public void SetPoco2(Poco2 poco2)
  {
    Poco2 = poco2;
  }
}
public class Poco2
{
  public int Id {get; private set;}
  ...
  public int Poco1Id {get; private set;}
  public Poco1 Poco1 {get; private set;}
}

And then setting them up:

async Task SomeMethod()
{
  var poco1 = new Poco1();
  var poco2 = new Poco2();
  poco1.SetPoco2(poco2);

  //at first only poco1 has reference to poco2
  //and poco2 does not have reference to poco1 yet
  Debug.Assert(poco1.Poco2 == poco2);
  Debug.Assert(poco2.Poco1 != poco1);


  await _someRepository.AddAsync(poco1);
  await _someRepository.SaveChnagesAsync();

  //After saving changes EF core manages the primary keys and references
  Debug.Assert(poco1.Poco2 == poco2);
  Debug.Assert(poco2.Poco1 == poco1);
}

and modelBuilder:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
  var poco1Builder = modelBuilder.Entity<Poco1>();
  poco1Builder.HasKey(x => x.Id);
  poco1Builder
    .HasOne(x => x.Poco2)
    .WithOne(x => x.Poco1)
    //Poco2 will have Poco1Id in db that will be used for reference
    //Also Poco1Id does not have to be set manually, EF core takes care of that
    .HasForeignKey<Poco2>(x => x.Poco1Id);
  ;

  var poco12Builder = modelBuilder.Entity<Poco2>();
}

Also if reference is needed before saving to db SetPoco2 can be modified as:

public void SetPoco2(Poco2 poco2)
{
  Poco2 = poco2;
  poco2.SetPoco1(this);
}

and set method in Poco2:

public void SetPoco1(Poco1 poco1)
{
  Poco1 = poco1;
}

Too way references are dangerous for big projects for poor maintainability

  • Related