1

I'm writing unit tests for a c#>Sql Server application. MS recommends using SQLite when not using a production db for testing (https://learn.microsoft.com/en-us/ef/core/testing/testing-without-the-database#inmemory-provider). I have SQLite setup and I'm trying to fill the db with test data. I exported a sample of production data into JSON format and saving that with the test project so I can update the test data fairly easily. The code to setup the SQLite context I took from the MS article

There's no line numbers to ref but this block

  var memoryData = new ReqestDataInMemory();
  var data = memoryData.AllRepositoryRequests();

gets the json and returns the complex object structure as an IEnumberable. I then try and save it to the db context.

    public class TestDbContext
    {
        public SqliteConnection _connection {get; set;} 
        public DbContextOptions<TaxonomyContext> _contextOptions { get; set;}

        public TestDbContext()
        {
            // Create and open a connection. This creates the SQLite in-memory database, which will persist until the connection is closed
            // at the end of the test (see Dispose below).
            _connection = new SqliteConnection("Filename=:memory:");
            _connection.Open();

            // These options will be used by the context instances in this test suite, including the connection opened above.
            _contextOptions = new DbContextOptionsBuilder<TaxonomyContext>()
                .UseSqlite(_connection)
                .Options;

            // Create the schema and seed some data
            using var context = new TaxonomyContext(_contextOptions);

            if (context.Database.EnsureCreated())
            {
                using var viewCommand = context.Database.GetDbConnection().CreateCommand();
                viewCommand.CommandText = @"
                    CREATE VIEW AllRequests AS
                    SELECT Url
                    FROM Request";
                viewCommand.ExecuteNonQuery();
            }

            var memoryData = new ReqestDataInMemory();
            var data = memoryData.AllRepositoryRequests();

            context.AddRange(data);
            context.SaveChanges();
        }

The AddRange() throw the error

System.InvalidOperationException : The instance of entity type 'RequestType' cannot be tracked because another instance with the same key value for {'RequestTypeId'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.

which makes sense if its trying to save the lookup table over and over. Hence my problem.

Here is the object I'm working with. RequestType is where I'm running into a blocker and its a simple lookup table RequestType.Id and RequestType.Name. Status is a one-to-many-lookup table. All have FK relationships back to Request

The ChangeDetail object is details about the request. It has unique IDs and a FK to the Request.

I tried NULLing both RequestType and Statuses but it s

 public class Request
    {
        public int RequestId { get; set; }
        /// <summary>changed from "navarchar(max)" to "text" for sqlite testing db </summary>
        [Required]
        [MinLength(50)]
        [Column(TypeName = "text")]
        public string Justification { get; set; } = string.Empty;
        [Required]
        public int SubmitUser { get; set; }
        [Required]
        public DateTime SubmitDate { get; set; }
        public int? ModifyUser { get; set; }
        public DateTime? ModifyDate { get; set; }
        public virtual IEnumerable<ChangeDetail> ChangeDetail { get; set; } = new List<ChangeDetail>();
        public virtual IEnumerable<Status> Statuses { get; set; } = new List<Status>();
        public virtual RequestType RequestType { get; set; } = new RequestType();
    }

My queries work great so I think all my relationships are solid. Just in case it helps, here are the relationships.

       protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Request>()
                .HasOne(x => x.RequestType)
                .WithMany(x => x.Requests);

            modelBuilder.Entity<Request>()
                .HasMany(x => x.ChangeDetail)
                .WithOne(x => x.Request);

            modelBuilder.Entity<Request>()
                .HasMany(x => x.Statuses)
                .WithMany(x => x.Requests);

            modelBuilder.Entity<Status>()
                .HasOne(x => x.RequestStatusType)
                .WithMany(x => x.Statuses);
        }

To solve the problem I tried this: LINQ query to return distinct field values from list of objects

I thought this showed promise, using Entity.Unmodified but couldn't get it to work Save detached entity in Entity Framework 6

This talks about the FK, which I have setup but maybe its not referencing them on save? How do I stop Entity Framework from trying to save/insert child objects?

I appreciate any suggestions you may have.

USMC6072
  • 268
  • 2
  • 14
  • Problem in method that you have hidden. `AllRepositoryRequests ` generates duplicate objects `RequestType` which have the same Id. – Svyatoslav Danyliv Jun 13 '23 at 07:30
  • yes, that is correct. In Request Type is repeated because its a lookup in the prod db. If I try and "seed" the in-memory db with the lookup values, it still tries to save the Request Type object which I'm trying to stop. – USMC6072 Jun 13 '23 at 14:13
  • Do not create entity with the same id. Create one and reuse in other references. Or if it is already in database, mark it as `Unchanged` in ChangeTracker. But it still should be one instance. – Svyatoslav Danyliv Jun 14 '23 at 04:12

1 Answers1

0

I thought I would close the loop on this. It took me a while but it was a data issue in one of the joining tables for the many-to-many relationships. Once I fix

USMC6072
  • 268
  • 2
  • 14