我正在导入一个巨大的文件,这使我创建了许多模块与他们的产品在1对N的关系.

然而,可能存在错误(异常),并且这些异常与产品之间存在N对N的关系:

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

    public ICollection<Product> Products { get; set; } = new List<Product>();
}

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

    public int ModuleId { get; set; }
    public Module? Module { get; set; }

    public ICollection<Anomalie> Anomalies { get; set; } = new List<Anomalie>();
}

public class Anomalie
{
    public int Id { get; set; }
    public ICollection<Product> Products { get; set; } = new List<Product>();
}

重要的是要注意,数据库中已经存在异常,我不会创建新的异常,只将它们链接到产品.

下面我有一个通用的AddBulkAsync方法,我在其中发送我的实体以插入它:

public virtual async Task<IList<T>> AddBulkAsync(ICollection<T> entities)
{
    try
    {
        await _dbContext.Set<T>().AddRangeAsync(entities);
        await _dbContext.SaveChangesAsync();
    
        _logger.LogInformation($"Entité ajoutée avec succès - {typeof(T).Name}");
        return entities.ToList();
    }
    catch (Exception ex)
    {
        _logger.LogError($"Impossible d'ajouter l'entité {typeof(T).Name} : {ex.Message}");
    }
   
    return new List<T>();
}

简而言之,假设我只插入了一个实体的列表:

var modulesToInsert = new List<Module>() {
     new Module() {
        Products = new List<Product>() {
            new Product() { ... },
            new Product() { ... },
            new Product() { ... },
        }
    }
};

await AddBulkAsync(moduleToInsert); // This works

// ----------------------------------------------------------
var anomalie1 = await GetAnomalieWithIdAsync(1); //get from db
var anomalie2 = await GetAnomalieWithIdAsync(2); //get from db

var modulesToInsert2 = new List<Module>() {
     new Module() {
        Products = new List<Product>() {
            new Product() { Anomalies  = new List<Anomalie>() { anomalie1, anomalie2 } },
            new Product() { Anomalies  = new List<Anomalie>() { anomalie1, anomalie2 } },
        }
    }
};

await AddBulkAsync(moduleToInsert2); // This doesn't works

这里我得到一个错误,告诉我实体AnomalieId = 1已经被跟踪.

我试图用Id = 1创建一个空对象Anomalie,但我得到了同样的错误.

我还需要继续使用AddRange,因为我可以插入很多实体,有什么建议吗?

推荐答案

首先,当涉及到EF实体时,不要使用通用模式,如通用存储库.泛型的工作原理是,它们的行为100%独立于它们包装的类型.应该清楚的是,对于给定的操作,某些类的行为将不同于其他类.有些类需要处理导航属性,有些则不需要.我不建议为了节省几行简单的EF代码而试图用条件逻辑找到一个泛型解决方案.

关于对现有实体的引用,是的,这些绝对需要特别处理.不过,从您的示例中,我怀疑这实际上不是您正在运行的代码,因为它可能会按照编写的方式工作.我会看看这个GetAnomalieWithIdAsync(1)方法实际上在做什么.要让这样的代码正常工作,您需要确保两件事:

  1. GetAnomalieWithIdAsync使用的DbContext实例与上面的代码插入产品时使用的实例相同.
  2. GetAnomalieWithIdAsync是使用AsNoTracking或以其他方式分离读取异常的not.

当开发人员将分离或反序列化的实体传递到方法中,并且只将它们关联到要保存的新产品时,而不考虑DbContext没有跟踪这些实例时,更常见的情况是出现此错误.DbContext不知道引用的实体是否表示数据库中的现有记录,或者是否应被视为与要添加的项关联的新项,除非它恰好跟踪该实体.例如,它不会根据实体是否设置了主键或是否为默认实体来进行假设.

如果要在多个产品中引用相同的现有实体实例,则需要确保在所有产品中使用对该实体的完全相同的引用.例如:

// won't work:
products.Add(new Product { Anomalies = new[] { New Anomalie { Id = 1 } }.ToList());
products.Add(new Product { Anomalies = new[] { New Anomalie { Id = 1 } }.ToList());

// better:
var anomalie = new Anomalie { Id = 1 };
products.Add(new Product { Anomalies = new [] { anomalie });
products.Add(new Product { Anomalies = new [] { anomalie });

但是,由于该异常表示现有记录,我们需要确保DbContext正在跟踪它.这要么意味着从我们用来插入这些产品的DbContext实例加载它:

var anomalie = await _dbContext.Anomalies.SingleAsync(x => x.Id == 1);
product.Anomaies.Add(anomalie);    

...或者关联一个"存根"或分离的实例,只要DbContext还没有跟踪引用.例如,如果我们的方法传入一个分离的异常引用:

var existingAnomalie = _dbContext.Anomalies.Local.FirstOrDefault(x => x.Id == anomalie.Id);
if (existingAnomalie != null)
    anomalie = existingAnomalie;
else
    _dbContext.Attach(anomalie);

product.Anomaies.Add(anomalie);    

基本上,每当给定分离的引用时,我们都会访问.Local跟踪缓存,以查看DbContext是否正在跟踪引用.如果是这样的话,我们应该使用这个引用.如果没有,那么我们可以附加这个引用并使用它.

一个重要提示:当您有一个具有基于集合的导航属性的类时,将setter设置为protected以避免如下代码:

product.Anomalies = new List()

在构建新产品时,这样的东西是无害的,但如果它被用于现有的产品实体引用,它将与EFBundle 在一起.

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

    public int ModuleId { get; set; }
    [Required]
    public Module? Module { get; set; }

    public ICollection<Anomalie> Anomalies { get; protected set; } = new List<Anomalie>();
}

我建议使用构造函数或工厂方法来构建实体,特别是当它们需要关系时.您可以使用一个protected的默认构造函数来让EF满意.

public Product(Module module, params Anomalie[] anomalies)
{
    Module = module;
    ModuleId = module.Id;
    Anomalies = anomalies.ToList();
}

// Available for EF
protected Product() {}

如果使用这样的构造函数来传递异常引用,您首先需要确保DbContext愉快地跟踪它们,以避免像您遇到的那样的错误.

Csharp相关问答推荐

如何使用Automapper映射两个嵌套列表

System.Data.SQLite:判断SQLite数据库是否为空(任何表中至少有一行)

等待限制选项似乎不适用于频道

返回TyedResults.BadRequest<;字符串>;时问题详细信息不起作用

在命名管道上使用GRPC ASP.NET核心时如何配置命名管道权限

附加标题不起作用,而添加则起作用

将字节转换为 struct 并返回

HttpRequestMessage.SetPolicyExecutionContext不会将上下文传递给策略

如何在用户在线时限制令牌生成?

如何在我的C#应用程序中设置带有reactjs前端的SignalR服务器?

EF核心新验证属性`DeniedValues`和`StringCompison`不起作用

正在从最小API-InvocationConext.Arguments中检索参数的FromBodyAttribute

C#如何获取字符串中引号之间的文本?

如何对特定异常使用Polly重试机制?

Foreach非常慢的C#

SqlException:无法打开数据库.升级到Dotnet 8后-数据库兼容性版本-非EFCore兼容性级别

如何在更新数据库实体时忽略特定字段?

为什么我的UserControl没有加载到我的主窗口中?

如何在flutter dart中使用publicKey.xml文件进行rsa加密,我遇到了问题Exception:Could not parse BigInt

异步等待,如何在Windows窗体中使用它们?