EF Core with Lazy Loading tracks unreachable Objects? - asp.net

I am currently having troubles with entity framework core.
The application I am developing is supposed to help users plan their next business year by increasing/decreasing the quantity of a service they want to provide in the next year.
Based on their input the "worth" of a service is distributed pro rata to other "mini-services" that are contained in the changed service.
To do so I load the affected entries of the main service and the "mini-services" from Database via a repository which then uses Entity Framework.
public IEnumerable<OpsDistributionEntry> FilteredOpsDistributionEntries(int catalogId, IEnumerable<OpsDistributionEntry> filterEntries)
{
return _context.OpsDistributionEntries.FromSqlRaw(
$"SELECT * FROM OpsDistributionEntries WHERE (Id IN (SELECT OpsEntriesId FROM DistributionCatalogOpsDistributionEntry WHERE CatalogsId = {catalogId}) " +
$"AND EntityId IN ({string.Join(",", filterEntries.Select(x => x.EntityId))}))").ToList();
}
I then map those database objects to my domain objects via constructor.
var opsDistributionEntries = new OpsDistributionEntriesFromDatabaseObjects(
_repository.FilteredOpsDistributionEntries(_distCatalogId, _filterEntries));
public class OpsDistributionEntriesFromDatabaseObjects : IOpsDistributionEntries
{
private readonly IOpsDistributionEntries _distribution;
public OpsDistributionEntriesFromDatabaseObjects(IEnumerable<DatabaseObjects.OpsDistributionEntry> distribution)
{
_distribution = new OpsDistributionEntries(distribution.Select(x => new OpsDistributionEntryFromDatabaseObject(x)));
}
public IEnumerator<IOpsDistributionEntry> GetEnumerator()
{
return _distribution.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public class OpsDistributionEntryFromDatabaseObject : IOpsDistributionEntry
{
public OpsDistributionEntryFromDatabaseObject(DatabaseObjects.OpsDistributionEntry opsDistributionEntry)
: this(opsDistributionEntry.Id, opsDistributionEntry.TotalCases, opsDistributionEntry.TotalEffectiveWeight, opsDistributionEntry.Provide,
opsDistributionEntry.Freeze,
new OpsFromDatabaseObject(opsDistributionEntry.Entity),
new DrgDistributionsFromDatabaseObjects(opsDistributionEntry.DrgDistribution))
{
}
private OpsDistributionEntryFromDatabaseObject(int id, int totalCases, double totalEffectiveWeight, bool provide, bool freeze, IOps ops,
IDrgDistributions drgDistribution)
{
Id = id;
TotalCases = totalCases;
TotalEffectiveWeight = totalEffectiveWeight;
Provide = provide;
Freeze = freeze;
Ops = ops;
DrgDistribution = drgDistribution;
}
public int Id { get; }
public int TotalCases { get; }
public double TotalEffectiveWeight { get; }
public bool Provide { get; }
public bool Freeze { get; }
public IOps Ops { get; }
public IDrgDistributions DrgDistribution { get; }
}
public sealed class OpsFromDatabaseObject : IOps
{
public OpsFromDatabaseObject(DatabaseObjects.Ops ops) : this(ops.Id, ops.Code, ops.Description, ops.Year)
{
}
private OpsFromDatabaseObject(int id, string code, string description, int year)
{
Id = id;
Code = code;
Description = description;
Year = year;
}
public int Id { get; }
public string Code { get; }
public string Description { get; }
public int Year { get; }
}
I pass the database objects on to different levels, but finally every value is assigned and every possible navigation property is mapped to an domain object.
With those mapped domain objects I recalculate the new "worth" of the service and the correlated "mini-services".
After calculation I again map my Domain Objects to DatabaseObjects.
DatabaseObjects.OpsDistributionEntry ToDatabaseObject() => new DatabaseObjects.OpsDistributionEntry
{
Id = Id,
EntityId = Ops.Id,
Freeze = Freeze,
Provide = Provide,
TotalCases = TotalCases,
TotalEffectiveWeight = TotalEffectiveWeight,
DrgDistribution = DrgDistribution.Select(x => x.ToDatabaseObject()).ToImmutableList(),
};
When I want to add those "updated" Objects to the context via repository
public void UpdateDistributionEntries(IEnumerable<OpsDistributionEntry> opsDistributionEntries)
{
if (opsDistributionEntries == null) throw new ArgumentNullException(nameof(opsDistributionEntries));
_context.OpsDistributionEntries.UpdateRange(opsDistributionEntries);
}
I am getting an Error that the Entities I want to updated are already being tracked by Entity Framework.
After some debugging I think that EF is still tracking the database objects I loaded for mapping the domain objects. I just use the database objects to map values to the domain objects and do not store any reference for them (as far as I understand).
Can any of you maybe tell me why they are still being tracked even if they are "unreachable". Or am I thinking wrong? Might this be because of Lazy Loading?
I've been debugging for almost 14 hours now :D Please someone give me a hint :D
Many thanks in advance

Related

EFCore DotNet 5 & Automapper. Map virtual collections

Firstly, I am new to Automapper and finding the official documentation poor to say the least.
I am trying to map two complex entities with virtual collections which themselves need to be mapped
Map<IEnumerable<ComplexEntityDb>, IEnumerable<ComplexEntityVM>>
within each row I need to map
Map<IEnumerable<EntityCollectionItemDb>, IEnumerable<EntityCollectionItemVM>>
A very simplified version of the classes:
public class ComplexEntityDb
{
public int Id;
// Multiple properties (14) removed for brevity
public virtual ICollection<EntityCollectionItemDb> CollectionDb { get; set; }
}
public class ComplexEntityVM
{
public int Id;
// Multiple properties (7) removed for brevity
public virtual ICollection<EntityCollectionItemVM> CollectionDb { get; set; }
}
public class EntityCollectionItemDb
{
public int Id;
// Multiple properties (12) removed for brevity
}
public class EntityCollectionItemVM
{
public int Id;
// Multiple properties (6) removed for brevity
}
What is the correct way to do this with EF Core 5.04 on dotnet 5 using AutoMapper 10.1.1 and DI extensions 8.1.1
I have read dozens of articles on this site and there does not seem to be an easy way to do this.
Many thanks in advance for any help much wiser souls can give.
EDIT: (taken from the comment section of #dglozano's answer) -
I am using mapping profile -
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<ComplexEntityDb, ComplexEntityVM>().ReverseMap();
CreateMap<EntityCollectionItemDb, EntityCollectionItemVM>).ReverseMap();
}
}
and IMapper -
private readonly IMapper _mapper;
public CustomService(IMapper mapper)
{
_mapper = mapper;
}
and trying -
return _mapper.Map<IEnumerable<ComplexEntityDb>, IEnumerable<ComplexEntityVM>>(source);
Result: EntityCollectionItemVM collection is empty.
You just need to define the mappings of each element type, as the documentation for Nested Mappings suggests. You have to tell Automapper how to map ComplexEntityDb -> ComplexEntityVM and how to map EntityCollectionItemDb -> EntityCollectionItemVM, and then all the mappings of collections of those items will automatically be supported.
So in your case, a minimal example would look like this:
using System;
using AutoMapper;
using System.Collections.Generic;
public class ComplexEntityDb
{
public ComplexEntityDb(int id)
{
Id = id;
var item1 = new EntityCollectionItemDb();
var item2 = new EntityCollectionItemDb();
item1.Id = id * 1000 + 1;
item2.Id = id * 1000 + 2;
CollectionDb = new List<EntityCollectionItemDb>{item1, item2, };
}
public int Id
{
get;
set;
}
// Multiple properties (14) removed for brevity
public ICollection<EntityCollectionItemDb> CollectionDb
{
get;
set;
}
}
public class ComplexEntityVM
{
public int Id;
// Multiple properties (7) removed for brevity
public ICollection<EntityCollectionItemVM> CollectionDb
{
get;
set;
}
}
public class EntityCollectionItemDb
{
public int Id;
// Multiple properties (12) removed for brevity
}
public class EntityCollectionItemVM
{
public int Id;
// Multiple properties (6) removed for brevity
}
public class Program
{
public static void Main()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ComplexEntityDb, ComplexEntityVM>();
cfg.CreateMap<EntityCollectionItemDb, EntityCollectionItemVM>();
});
var complexEntity1 = new ComplexEntityDb(1);
var complexEntity2 = new ComplexEntityDb(2);
var complexEntity3 = new ComplexEntityDb(3);
var source = new List<ComplexEntityDb>{complexEntity1, complexEntity2, complexEntity3};
var mapper = config.CreateMapper();
var dest = mapper.Map<IEnumerable<ComplexEntityDb>, IEnumerable<ComplexEntityVM>>(source);
foreach(var parentMapped in dest)
{
Console.WriteLine("Mapped parent Id {0}", parentMapped.Id);
foreach(var childMapped in parentMapped.CollectionDb)
{
Console.WriteLine(" - Mapped child Id {0}", childMapped.Id);
}
Console.WriteLine();
}
}
}
Output:
Mapped parent Id 1
- Mapped child Id 1001
- Mapped child Id 1002
Mapped parent Id 2
- Mapped child Id 2001
- Mapped child Id 2002
Mapped parent Id 3
- Mapped child Id 3001
- Mapped child Id 3002
Try it out in this fiddle.
In a more real scenario, it would be the same idea, but I would create a Profile(s) to define each of the mappings configurations and then use the injected IMapper to do execute the mapping.

SQLite.NET PCL returning 0 in all instances of autoincrement primary key

I am totally not getting this, because I have used this library in Xamarin apps for several years.
I have this base class that contains properties common in all db items:
public class BaseItem
{
[PrimaryKey, AutoIncrement]
public int ID { get; set; } = 0; // SQLite ID
public long CreatedTimeSeconds { get; set; } = DateTime.Now.ToUnixTimeSeconds();
public long ModifiedTimeSeconds { get; set; } = DateTime.Now.ToUnixTimeSeconds();
}
Now, I derive from it:
[Table("CategoryTable")]
public class Category : BaseItem
{
public int CategoryTypeID { get; set; } = (int)CategoryType.Invalid;
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
}
Here's a simplified version of what I'm seeing:
public class DBWorld
{
ISQLiteService SQLite { get { return DependencyService.Get<ISQLiteService>(); } }
private readonly SQLiteConnection _conn;
public DBWorld()
{
_conn = SQLite.GetConnection("myapp.sqlite");
}
public void TestThis()
{
_conn.CreateTable<Category>();
var category = new Category();
category.Name = "This Should Work";
int recCount = connection.Insert(category);
// at this point recCount shows as 1, and category.ID shows as zero.
// I thought Insert was supposed to set the autoincrement primary key
// regardless, it should be set in the database, right? So...
var categoryList = connection.Query<Category>($"SELECT * FROM {DBConstants.CategoryTableName}");
// at this point categoryList[0] contains all the expected values, except ID = 0
}
}
I am obviously missing something, but for the life of me, I can't figure out what...
Like so many other bizarre things that happen in the Visual Studio Xamarin world, when I went back later, this worked the way all of us expect. I guess Visual Studio was just tired and needed to be restarted.

Auto Mapper Constructor initialization Mapping Issue

I have the following Mapping configurations:-
Initialized Data:-
private static IEnumerable<Source> InitializeData()
{
var source= new[]
{
new Source("John", "Doe", "1111111111"),
new Source("Jack", "Handsome", "2222222222"),
new Source("Joe", "Mackenze", "3333333333")
};
return source;
}
Source Model:
public class Source
{
private string First { get; set; }
private string Last { get; set; }
private string Phone { get; set; }
public Source(string first, string last, string phone)
{
First = first;
Last = last;
Phone = phone;
}
}
Destination Model
public class Destination
{
public string First { get; set; }
public string Last { get; set; }
public string Phone { get; set; }
}
Main
static void Main(string[] args)
{
var config = new MapperConfiguration(cfg =>
{
cfg.AllowNullCollections = true;
cfg.CreateMap<Source, Destination>().ReverseMap();
});
var mapper = new Mapper(config);
var source= InitializeData();
var people = mapper.Map<IEnumerable<Destination>>(source);
foreach (var p in people)
{
Console.WriteLine("Name: {0}-{1} Phone: {2}", p.First, p.Last, p.Phone);
}
Console.ReadLine();
}
Problem descriptions:
I have been struggled to understand the AutoMapper mapping between source and destination models.
My source model has a constructor to initialize or accept data from outside. It works fine when I removed the source constructor from the model that's mean flat mapping works fine but constructor initialization has the issue. When I debug in VS2019, it shows the number of records but all fields are empty/null.
What is wrong with the above mapping. I have gone through the AutoMapper reference docs but do not get a hold on this issue.
I highly appreciate your help!
Try calling AssertConfigurationIsValid. Check http://docs.automapper.org/en/latest/Configuration-validation.html.
Your Source properties are private. I assume you meant public.

How do you set some properties and leave others as defaults using AutoFixture and AutoMoqCustomization?

I am new to AutoFixture so I hope you can help. How do you set some properties in an object but leave others as the AutoFixture default - while using XUnit's [Theory] attribute and an AutoDataAttribute.
For example, in the contrived Airport example below based on Jason Robert's Pluralsight course, when setting the property (or the Airport object) e.g.
f.Customize<Mock<IAirport>>(c => c.Do(m => m.SetupGet(i => i.code).Returns("NOO")));
the other properties are often null, or I have to manually set them rather than letting AutoFixture do it. I would prefer to have cleaner code where the fixtureFactory sets all the properties for the Airport so that the V2 unit test only passed in a single Airport parameter.
So, within the fixtureFactory
How do you set MULTIPLE properties?
How does one use the default AutoFixture values rather than leaving the uninitialized values as
null?
Thanks!
using AutoFixture;
using AutoFixture.AutoMoq;
using AutoFixture.Xunit2;
using Moq;
using System;
using Xunit;
namespace AirportTesterWithAutoFixture
{
public interface IAirport
{
string city { get; set; }
string code { get; set; }
string country { get; set; }
string name { get; set; }
void CallAirTrafficControl();
}
public class Airport : IAirport
{
public string name { get; set; }
public string city { get; set; }
public string code { get; set; }
public string country { get; set; }
public Airport()
{
}
public Airport(string name, string code, string country, string city)
{
this.name = name;
this.code = code;
this.country = country;
this.city = city;
}
public void CallAirTrafficControl()
{
if (this.country.Equals("Canada") && this.code.StartsWith("Y"))
{
// Send "Bonjour!"();
}
else
{
throw new Exception("Invalid code for Canada");
}
}
}
public class UnitTest1
{
[Fact]
public void V1_Validate_ExceptionThrown_ForInvalidCanadianAirportCode()
{
var fixture = new Fixture();
var sut = fixture.Create<Airport>();
// Overwrite code and country with invalid setting for Canada.
sut.country = "Canada";
sut.code = "NOT";
Assert.ThrowsAny<Exception>(() => sut.CallAirTrafficControl());
}
[Theory]
[AutoMoqInvalidAirportDataAttribute]
public void V2_Validate_ExceptionThrown_ForInvalidCanadianAirportCode(IAirport sut, string name, string city)
{
Airport airport = new Airport(name, sut.code, sut.country, city);
Assert.ThrowsAny<Exception>(() => airport.CallAirTrafficControl());
}
}
// https://stackoverflow.com/questions/58998834/how-to-use-ifixture-buildt-with-automoqcustomization-when-t-is-an-interface
public class AutoMoqInvalidAirportDataAttribute : AutoDataAttribute
{
public static Func<IFixture> fixtureFactory = () =>
{
IFixture f = new Fixture().Customize(new AutoMoqCustomization());
f.RepeatCount = 5;
// How do you set MULTIPLE properties?
// How does one use the default AutoFixture values rather than leaving the uninitialized values as null?
// Can one pass a custom property used earlier in the Fixture creation process to another custom property used later?
f.Customize<Mock<IAirport>>(c => c.Do(m => m.SetupGet(i => i.code).Returns("NOT")));
return f;
};
public AutoMoqInvalidAirportDataAttribute() : base(fixtureFactory)
{
}
}
}
AutoFixture does not populate mock properties by default, but it can be done. These blog posts describe how to do it:
https://blog.ploeh.dk/2013/04/05/how-to-configure-automoq-to-set-up-all-properties/
https://blog.ploeh.dk/2013/04/08/how-to-automatically-populate-properties-with-automoq/
Author of AutoFixture does not recommend this approach, however, as he considers declaration of properties in interfaces a design smell.
I could not find the original discussion about this topic unfortunately, but it is hidden somewhere on StackOverflow in the comments. Maybe you will be able to find it if you go through Mark Seemann's profile.

Problem using FluentNHibernate, SQLite and Enums

I have a Sharp Architecture based app using Fluent NHibernate with Automapping. I have the following Enum:
public enum Topics
{
AdditionSubtraction = 1,
MultiplicationDivision = 2,
DecimalsFractions = 3
}
and the following Class:
public class Strategy : BaseEntity
{
public virtual string Name { get; set; }
public virtual Topics Topic { get; set; }
public virtual IList Items { get; set; }
}
If I create an instance of the class thusly:
Strategy s = new Strategy { Name = "Test", Topic = Topics.AdditionSubtraction };
it Saves correctly (thanks to this mapping convention:
public class EnumConvention : IPropertyConvention, IPropertyConventionAcceptance
{
public void Apply(FluentNHibernate.Conventions.Instances.IPropertyInstance instance)
{
instance.CustomType(instance.Property.PropertyType);
}
public void Accept(FluentNHibernate.Conventions.AcceptanceCriteria.IAcceptanceCriteria criteria)
{
criteria.Expect(x => x.Property.PropertyType.IsEnum);
}
}
However, upon retrieval (when SQLite is my db) I get an error regarding an attempt to convert Int64 to Topics.
This works fine in SQL Server.
Any ideas for a workaround?
Thanks.
Actually, it is possible to map enums to INT, but in SQLite, INT will come back as an Int64, and, since you can't specify that your enum is castable to long, you will get this error. I am using NHibernate, so my workaround was to create a custom AliasToBean class that handles converting the enum fields to Int32:
public class AliasToBeanWithEnums<T> : IResultTransformer where T : new()
{
#region IResultTransformer Members
public IList TransformList(IList collection)
{
return collection;
}
public object TransformTuple(object[] tuple, string[] aliases)
{
var t = new T();
Type type = typeof (T);
for (int i = 0; i < aliases.Length; i++)
{
string alias = aliases[i];
PropertyInfo prop = type.GetProperty(alias);
if (prop.PropertyType.IsEnum && tuple[i] is Int64)
{
prop.SetValue(t, Convert.ToInt32(tuple[i]), null);
continue;
}
prop.SetValue(t, tuple[i], null);
}
return t;
}
#endregion
}
Usage:
public IList<ItemDto> GetItemSummaries()
{
ISession session = NHibernateSession.Current;
IQuery query = session.GetNamedQuery("GetItemSummaries")
.SetResultTransformer(new AliasToBeanWithEnums<ItemDto>());
return query.List<ItemDto>();
}
By default, emums are automapped to strings for SQLite (don't know what happens with SQL Server).
Less efficient storage wise, obviously, but that might be a non-issue unless you have really huge data sets.

Resources